Google Search Console integration for Model Context Protocol (MCP)
Hermoso: Social Media Manager is installed from its publisher's own source and answers where it runs, so this marketplace is not in the path of a single call. There is no address here to send one to, and a panel that pretended otherwise would be showing you an answer we made up. Install it and call it from your own client — the Installation tab has the entry for each one.
What it does
AI marketing agent for social: post, schedule and answer comments and DMs across 15 channels.
Quickstart
# 1 — add it to Claude Code
claude mcp add --scope user --transport http hermoso-social-media-manager-mcp 'https://app.hermoso.ai/mcp?tools=app-social&src=registry'
# 2 — ask your agent for something these tools do
# enable_tools, find_tools, call_tool and 421 more
Hermoso: Social Media Manager costs nothing on mcprush: there is no plan to choose here, no cap to set and nothing mcprush can bill you for.
Collected from a public index. Nobody has claimed this account, so nothing here was written by its author — claim it if it is yours.
Where are you running it?
Every route below installs the same thing and ends at the same approval screen. Nothing here runs on your machine and nothing runs on ours: this server answers at its publisher’s own address, and what you install is the connection straight to it.
This is a public server: it answers at its publisher’s own address and this marketplace is not in the path. Claude Code registers it in one command; --scope user makes it available in every project. The transport below is http, as the publisher's own manifest declares it.
claude mcp add --scope user --transport http hermoso-social-media-manager-mcp 'https://app.hermoso.ai/mcp?tools=app-social&src=registry'Reconnect, or start a new session, and the tools appear in the model’s tool list.
One config entry pointing straight at the publisher’s own address — this marketplace is not in the path, and nothing from this listing executes on your machine.
424 tools, with what each one reads, writes and reaches shown before you agree — the same list on every route above. Read the tool surface.
Tool surface
What the model actually sees. Descriptions are diffed on every release — see version history.
LIST a group of tools that is not in this session's roster. IT IS NOT HOW YOU REACH A TOOL — call_tool runs any Hermoso tool whether or not it is listed, and that works everywhere. Use this when the session will use MANY tools from one area and you want them in your list. WORKS ON CLIENTS THAT RE-READ THE TOOL LIST (stdio, the CLI, Cursor, Claude Code); a host that fixed its roster at connect time
| Parameter | Type | Required | Notes |
|---|---|---|---|
| groups | array | yes | Groups to switch on, e.g. ['ads']. Unknown names are refused by name rather than ignored. |
Search EVERY Hermoso tool — your starting list is deliberately short, and everything else in the product is here — by name, task or group. Each row gives the tool's PARAMETERS in one line, its CREDIT COST (free means free on every plan; a tool that runs a model quotes the live per-model figure) and its recent HEALTH on this server (failure rate and typical duration, or "no recent calls", which mea
| Parameter | Type | Required | Notes |
|---|---|---|---|
| query | string | no | words from the task or the tool name, e.g. "lead form", "whatsapp", "google ads keyword", "meta insights" |
| group | string | no | limit to one group: core, research, create, channels, channel_admin, analytics, ads, files, workspace |
| limit | number | no | how many to return (default 12, max 40) |
| onlyHealthy | boolean | no | leave out tools that are failing their recent calls or that need a connector this workspace has not made. Default false — nothing is hidden unless you ask, because a missing row reads as a missing cap |
Run ANY Hermoso tool by name — including the paid-campaign, analytics and channel-admin tools that are not in this session's starting list — with the same permissions, the same account and the same result as calling it directly. Get the exact name and its args from find_tools first. This is the route on hosts that cannot reload their tool list mid-conversation (claude.ai, ChatGPT): enable_tool
| Parameter | Type | Required | Notes |
|---|---|---|---|
| name | string | yes | name |
| args | object | no | the tool's arguments as an object, exactly as its own schema takes them |
Use this when the user wants to post to Bluesky. Publishing costs no credits. Publish a post to Bluesky as the connected account. Text up to 300 characters — Bluesky ALSO caps a post at 3000 UTF-8 bytes, so an emoji-heavy post can be under 300 characters and still be refused; Hermoso checks both before spending the round trip and says which limit and by how much. @MENTIONS written as the full hand
| Parameter | Type | Required | Notes |
|---|---|---|---|
| account | string | no | WHICH connected account of this channel to post as — its @handle or id from list_connector_accounts. Needed only when the profile has more than one bluesky account connected (several and none named is |
| brand | string | no | WHICH PROFILE this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no profile, or two, is R |
| hook | string | no | The post's angle |
| subject | string | no | what the post is about |
| recipe | string | no | the post's FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format |
| ideaId | string | no | short id of the content-plan idea this post came from |
| idempotencyKey | string | no | any stable string: a repeat within 24h returns the original post instead of posting again |
| allowDuplicate | boolean | no | post it even though an identical post was just made |
| text | string | yes | The post, up to 300 characters / 3000 UTF-8 bytes. |
| imageUrls | array | no | Up to 10 public image URLs to attach (1–4 a standard image post, 5–10 a Bluesky gallery; more than 10 is refused, never cut). Cannot be combined with videoUrl. |
| altText | no | Alt text — an ARRAY, one per image in the same order, or a single STRING to describe every image with it. WRITE ONE: Bluesky’s own lexicon makes alt a REQUIRED property of every image, so a post wit | |
| videoUrl | string | no | One public MP4 URL. Cannot be combined with imageUrls. Bluesky transcodes it, which takes a minute or two; a URL, not a local file path; use upload_file for local files |
| videoAlt | string | no | Alt text describing the video, for accessibility. |
| captions | array | no | Up to 20 WebVTT caption tracks: [{lang:'en', url:'https://…/en.vtt'}] or [{lang:'en', content:'WEBVTT 00:00…'}]. Each file is capped at 20000 bytes. |
| langs | array | no | BCP-47 language tags, e.g. ['en']. |
| linkCard | no | Rich link card (app.bsky.embed.external). OMIT for the default (a card is built automatically when the post has a URL and no media). false never builds one. true builds one from the first URL in | |
| platformCover | boolean | no | VIDEO COVER. Bluesky has no cover setting and shows the video’s FIRST frame, so by default Hermoso checks it and, only when that frame is blank (a template ad’s empty opening card), sends Bluesky a co |
PERMANENTLY delete one of the connected Bluesky account's OWN posts. IRREVERSIBLE — the AT Protocol removes the record from the account's repo, there is no trash and no undelete, and the post's likes, reposts, replies and quotes go with it. Call it WITHOUT confirm first: nothing is deleted, and it reports the post's REAL text and its live like / repost / reply / quote counts read back from Bluesky
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| uri | string | yes | the post's AT-URI (at://did:plc:…/app.bsky.feed.post/…) as post_to_bluesky returned it, the handle form of the same URI for the CONNECTED account only (at://<its handle>/app.bsky.feed.post/…), or just |
| confirm | boolean | no | REQUIRED true — deletion is permanent and cannot be undone |
| confirmText | string | no | the post's own text as the unconfirmed call reported it — the first 40 characters is enough. Required once the post has any likes, reposts, replies or quotes. A post with no text asks for its cid inst |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Use this when the user wants to post to a Telegram channel or group. Publishing costs no credits. Publish to a Telegram channel, group or chat as the brand's own bot. WHICH CHAT IS ALWAYS REQUIRED AND IS NEVER GUESSED: pass chatId as the public channel's @username (e.g. @yourchannel) or its numeric id (a group is negative; a supergroup or channel starts with -100). There is no default and there ca
| Parameter | Type | Required | Notes |
|---|---|---|---|
| account | string | no | WHICH connected account of this channel to post as — its @handle or id from list_connector_accounts. Needed only when the profile has more than one telegram account connected (several and none named i |
| brand | string | no | WHICH PROFILE this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no profile, or two, is R |
| hook | string | no | The post's angle |
| subject | string | no | what the post is about |
| recipe | string | no | the post's FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format |
| ideaId | string | no | short id of the content-plan idea this post came from |
| idempotencyKey | string | no | any stable string: a repeat within 24h returns the original post instead of posting again |
| allowDuplicate | boolean | no | post it even though an identical post was just made |
| chatId | string | yes | REQUIRED — the destination: a public channel's @username, or the numeric chat id. Never guessed; ask the user, or use list_telegram_chats. |
| text | string | no | the message. ≤4096 characters on its own; ≤1024 once any image or video is attached. |
| imageUrl | string | no | one image (≤10MB after upload); a URL, not a local file path; use upload_file for local files |
| imageUrls | array | no | an ALBUM of 2–10 media, in order. Photos and videos may be mixed — Telegram allows it. |
| videoUrl | string | no | one video (≤50MB). Passed alongside imageUrls it joins the album as one more item; a URL, not a local file path; use upload_file for local files |
| disablePreview | boolean | no | suppress the link-preview card on a text-only post. Default is Telegram’s own behaviour (previews on). |
| silent | boolean | no | deliver without a notification sound (Telegram’s disable_notification). This is NOT a visibility setting — the message is just as visible. |
| buttons | array | no | URL BUTTONS under the post, Telegram's call to action: [{text:'Shop now', url:'https://…'}] one per row, or rows [[a,b],[c]]. URL buttons only (https://, http:// or tg://), optional style danger|succe |
| parseMode | string | no | 'HTML' to format the text with Telegram's HTML: <b>, <i>, <u>, <s>, <a href="…">, <code>, <pre>, <blockquote>, <tg-spoiler>. Omit for plain text (the default). In HTML mode a literal <, > or & in the |
| platformCover | boolean | no | VIDEO COVER. Omit it (the default) and Hermoso sets the video’s best frame — the same frame as its Library thumbnail — as the cover (Telegram’s in-chat video cover). true = send no cover and let the p |
| coverAtMs | number | no | THE VIDEO COVER in the chat, as ONE frame: milliseconds from the start (7000 = the frame at 7s). Sent as Telegram’s cover image; beats platformCover. |
Find the chat ids this Telegram bot can be addressed by. STATE THE LIMIT WHENEVER YOU USE IT: this is NOT the list of chats the bot belongs to — the Bot API publishes no such method — it is every chat that SENT the bot an update in the last 24 hours, which is as long as Telegram keeps an update. A channel the bot posts to every day but nobody messages will NOT appear here, and its absence means no
| Parameter | Type | Required | Notes |
|---|---|---|---|
| limit | number | no | how many recent updates to scan, 1–100 (default 100) |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Read what people have SENT to the connected Telegram bot — the newest message from each chat, newest chat first, so you can see who is waiting on a reply. Reply with post_to_telegram using the chatId shown. Warning: THIS IS A ROLLING 24-HOUR WINDOW, NOT AN INBOX. Telegram keeps undelivered updates for 24 hours and publishes NO history endpoint at all, so anything older is unrecoverable — never rep
| Parameter | Type | Required | Notes |
|---|---|---|---|
| limit | number | no | how many raw updates to scan, 1-100 (default 100). Messages are grouped per chat, so this is not the number of rows you get back. |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
PERMANENTLY delete one message the bot posted to a Telegram chat. Call it WITHOUT confirm first: nothing is deleted and you get a sentence to show the user. There is deliberately NO preview of the message — the Bot API has no method that reads one message back, so anything shown would be invented, and for the same reason the result after deleting is Telegram’s own success answer rather than a veri
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| chatId | string | yes | the chat the message is in — the same @username or numeric id it was posted with |
| messageId | number | yes | the message id post_to_telegram returned (also the number at the end of a t.me link) |
| confirm | boolean | no | REQUIRED true — Telegram has no trash and no undelete |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Use this when the user wants to publish, draft or schedule a BLOG POST on their WordPress site. Publishing costs no credits. A title is REQUIRED and the text becomes the body: plain text or light markdown (## headings, - lists, bold, [links](url)) is turned into WordPress blocks, and HTML is sent as written. status: publish (default), draft, pending (submit for review), private (logged-in edit
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no profile, or two, is R |
| hook | string | no | The post's angle |
| subject | string | no | what the post is about |
| recipe | string | no | the post's FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format |
| ideaId | string | no | short id of the content-plan idea this post came from |
| idempotencyKey | string | no | any stable string: a repeat within 24h returns the original post instead of posting again |
| allowDuplicate | boolean | no | post it even though an identical post was just made |
| title | string | yes | REQUIRED — the post title |
| text | string | no | the body: plain text, light markdown or HTML |
| status | string | no | default publish |
| date | string | no | ISO time to publish at; a future date schedules it on WordPress itself |
| excerpt | string | no | the summary under the title in lists and search |
| slug | string | no | the end of the post URL |
| categories | array | no | category names or ids (a missing name is created) |
| tags | array | no | tag names or ids (a missing name is created) |
| imageUrl | string | no | one image — becomes the featured image |
| imageUrls | array | no | several images: the first is featured, the rest go inline |
| videoUrl | string | no | one video, embedded in the post |
| altText | string | no | alt text for the images |
Change a WordPress post that already exists: title, body, excerpt, slug, status (publish a draft, unpublish to draft, make private, or schedule with a future date), categories, tags or the featured image. Only the fields passed change; the reply is WordPress's own copy of the post. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| postId | number | yes | the post id post_to_wordpress or list_wordpress_posts returned |
| title | string | no | — |
| text | string | no | the new body — replaces the old one |
| excerpt | string | no | — |
| slug | string | no | — |
| status | string | no | — |
| date | string | no | — |
| categories | array | no | — |
| tags | array | no | — |
| featuredImageUrl | string | no | a new featured image |
Delete a WordPress post. Call it WITHOUT confirm first: nothing is deleted and you get a sentence naming the post to show the user. By default it goes to WordPress's TRASH (restorable from Posts > Trash); permanent:true skips the trash and cannot be undone. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| postId | number | yes | — |
| permanent | boolean | no | skip the trash — cannot be undone |
| confirm | boolean | no | true only once the user agreed to the sentence the first call returned |
List the posts on the brand's WordPress site, newest first — published, scheduled, drafts, pending and private — each with its id, status, title and live URL. Free. WordPress publishes no per-post view counts, so this carries none.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| status | string | no | one status or a comma list: publish, future, draft, pending, private (default all) |
| search | string | no | — |
| page | number | no | — |
| perPage | number | no | 1–100, default 20 |
Read one WordPress post back — WordPress's own title, status, live URL, excerpt and body. Use it to confirm a post really published. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| postId | number | yes | — |
List the WordPress site's categories or tags (kind), most-used first, each with its id. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| kind | string | yes | — |
| search | string | no | — |
Create a category or tag on the WordPress site. (post_to_wordpress also creates any category or tag NAME it is given that the site lacks.) Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| kind | string | yes | — |
| name | string | yes | — |
| parent | number | no | a parent category id (categories only) |
| description | string | no | — |
Post to the user's Discord server. Free. With the Hermoso bot added it posts as the bot to channel (or the brand's default channel) with attachments up to 25 MB a message, replyTo, threadId (inside a thread), startThread (a thread under the post) and threadName (a forum channel); the messageId it returns ("<channel>/<message>") is what edit, delete and get_discord_message take. Without the bot it
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no profile, or two, is R |
| hook | string | no | The post's angle |
| subject | string | no | what the post is about |
| recipe | string | no | the post's FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format |
| ideaId | string | no | short id of the content-plan idea this post came from |
| idempotencyKey | string | no | any stable string: a repeat within 24h returns the original post instead of posting again |
| allowDuplicate | boolean | no | post it even though an identical post was just made |
| text | string | no | the announcement, ≤2000 characters |
| imageUrl | string | no | — |
| imageUrls | array | no | up to 10 files in one message |
| videoUrl | string | no | — |
| mentions | boolean | no | let @everyone / @here / role mentions ping people (off by default) |
| silent | boolean | no | post without a notification |
| threadName | string | no | required when the channel is a FORUM channel: the new thread title |
| username | string | no | webhook posts only: a different display name than the webhook's own |
| discordServer | string | no | HERMOSO BOT: which Discord server (id or name) when the bot is in several |
| channel | string | no | HERMOSO BOT: channel id or #name (list_discord_channels); unset = the default channel |
| replyTo | string | no | HERMOSO BOT: reply to this message id |
| threadId | string | no | HERMOSO BOT: post inside this existing thread |
| startThread | string | no | HERMOSO BOT: start a thread under the new post with this title |
Change the text of a Discord message Hermoso posted (≤2000 characters) — the bot's or the announcement webhook's. Pass messageId exactly as post_to_discord returned it. Mentions never ping on an edit. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| messageId | string | yes | the message id post_to_discord returned (a bot post's is "<channel>/<message>") |
| channel | string | no | the channel, when messageId is a bare id of a BOT post |
| text | string | yes | — |
PERMANENTLY delete a Discord message Hermoso posted (the bot's or the announcement webhook's). Call it WITHOUT confirm first: nothing is deleted and you get a sentence quoting the message to show the user. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| messageId | string | yes | — |
| channel | string | no | — |
| confirm | boolean | no | true only once the user agreed |
Read back a Discord message Hermoso posted: its text, link and REACTIONS — the only engagement Discord exposes (there is no view count). Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| messageId | string | yes | — |
| channel | string | no | — |
Post to the user's Slack. Free. With the Hermoso app installed it posts as the app to channel (or the brand's default channel), uploads images and video, replies in a thread (threadTs) or schedules in Slack (postAt, text only, ≤120 days); the messageId it returns ("<channel>/<ts>") is what edit_slack_message, delete_slack_message and get_slack_message take. Without the app it posts through the bra
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no profile, or two, is R |
| hook | string | no | The post's angle |
| subject | string | no | what the post is about |
| recipe | string | no | the post's FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format |
| ideaId | string | no | short id of the content-plan idea this post came from |
| idempotencyKey | string | no | any stable string: a repeat within 24h returns the original post instead of posting again |
| allowDuplicate | boolean | no | post it even though an identical post was just made |
| text | string | no | — |
| title | string | no | a header line above the text |
| imageUrl | string | no | — |
| imageUrls | array | no | — |
| videoUrl | string | no | — |
| altText | string | no | alt text for the images |
| slackWorkspace | string | no | HERMOSO APP: which Slack workspace (id or name) when the app is in several |
| channel | string | no | HERMOSO APP: channel id or #name (list_slack_channels); unset = the default channel |
| threadTs | string | no | HERMOSO APP: reply in this thread (the parent message ts) |
| replyBroadcast | boolean | no | HERMOSO APP: also show the thread reply in the channel |
| postAt | string | no | HERMOSO APP: schedule in Slack itself for this ISO time (≤120 days, text only; use schedule_post for files) |
| unfurlLinks | boolean | no | false = no link previews |
The Slack workspaces this brand added the Hermoso app to, and the channels in one of them (public, plus private ones the app was invited to with /invite @Hermoso), with the default channel marked. Use it before posting when the user has not named a channel, and to show them the choice. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| slackWorkspace | string | no | which workspace (id or name) when there are several |
| query | string | no | filter channel names |
Choose the DEFAULT Slack channel the Hermoso app posts to: post_to_slack, scheduled Slack posts and Autopilot use it whenever a post names no channel. Shared with everyone on the brand. Ask the user which channel; never pick one for them. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| slackWorkspace | string | no | — |
| channel | string | yes | channel id or #name from list_slack_channels |
Read a Slack channel's recent messages, newest first, for context — what the team is discussing before you write. A public channel is joined first if needed; a private one needs /invite @Hermoso. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| slackWorkspace | string | no | — |
| channel | string | no | channel id or #name; unset = the default channel |
| limit | number | no | how many, 1–200 (default 20) |
| oldest | string | no | only messages after this ts |
The Discord servers this brand added the Hermoso app to, and the channels in one of them (text, announcement and forum channels), with the default channel marked. Use it before posting when the user has not named a channel, and to show them the choice. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| discordServer | string | no | which server (id or name) when there are several |
| query | string | no | filter channel names |
Choose the DEFAULT Discord channel the Hermoso app posts to: post_to_discord, scheduled Discord posts and Autopilot use it whenever a post names no channel. Shared with everyone on the brand. Ask the user which channel; never pick one for them. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| discordServer | string | no | — |
| channel | string | yes | channel id or #name from list_discord_channels |
Read a Discord channel's recent messages, newest first, for context — what members are saying before you write. Other people’s text needs Discord’s Message Content intent; a message Discord withholds is marked contentHidden. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| discordServer | string | no | — |
| channel | string | no | channel id or #name; unset = the default channel |
| limit | number | no | how many, 1–100 (default 20) |
| before | string | no | page back from this message id |
Change the text of a Slack message the Hermoso app posted (≤4,000 characters, Slack's edit limit). messageId is what post_to_slack returned ("<channel>/<ts>"). A message posted through a pasted webhook has no id and cannot be edited. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| messageId | string | yes | — |
| text | string | yes | — |
| slackWorkspace | string | no | — |
PERMANENTLY delete a Slack message the Hermoso app posted. Call it WITHOUT confirm first: nothing is deleted and you get a sentence quoting the message to show the user. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| messageId | string | yes | — |
| slackWorkspace | string | no | — |
| confirm | boolean | no | true only once the user agreed |
Read back a Slack message the Hermoso app posted: text, link, REACTIONS and thread REPLIES — the engagement Slack exposes (there is no view count). Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| messageId | string | yes | — |
| slackWorkspace | string | no | — |
Messages the Hermoso app scheduled IN SLACK (post_to_slack with postAt) that have not posted yet. Posts queued with schedule_post are in list_scheduled instead. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| slackWorkspace | string | no | — |
| channel | string | no | — |
Cancel a message scheduled in Slack (from list_slack_scheduled). Slack refuses once it is within 60 seconds of posting. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| slackWorkspace | string | no | — |
| channel | string | yes | — |
| scheduledMessageId | string | yes | — |
Use this when the user asks for their unanswered comments, messages or reviews. EVERYTHING PEOPLE SAID TO THIS BRAND, across every connected channel, in one list: Facebook and Instagram comments, Threads replies and mentions, YouTube comments, Google Business reviews, Bluesky replies and mentions, and X mentions — plus DIRECT MESSAGES on Meta (Messenger and Instagram Direct), Instagram, Bluesky, X
| Parameter | Type | Required | Notes |
|---|---|---|---|
| sources | array | no | Limit to these sources: facebook, instagram, threads, meta_dm, instagram_dm, meta_webhook, threads_mention, youtube, google_business, x, bluesky, bluesky_mention, bluesky_dm, x_dm, telegram_dm, interc |
| postId | string | no | Restrict Facebook/Instagram to one post or media id (Meta reads comments per post). |
| videoId | string | no | Restrict YouTube to one video. |
| conversationId | string | no | Restrict DMs to one conversation (from list_meta_conversations, list_bluesky_convos or list_x_dms). Required for Bluesky and X DMs; on X reading one costs credits. |
| pageId | string | no | Facebook Page id (list_meta_pages) when the brand has several Pages connected: reads that Page's Messenger and Instagram DMs and comments only. Omit to read every connected Page. |
| limit | number | no | Max items per source (default 25). |
| unansweredOnly | boolean | no | Only items with no reply from the brand yet. |
Answer an inbox item BY ITS COMPOSITE ID — the id list_inbox returned, e.g. 'facebook:123_456' or 'google_business:accounts/1/locations/2/reviews/3'. Routes to the right channel for you; you do not need to know which reply tool a platform uses. The reply is posted as the brand — PUBLIC on every comment, reply, mention and review, and PRIVATE on the DM sources and on Intercom (it goes to the cust
| Parameter | Type | Required | Notes |
|---|---|---|---|
| id | string | yes | The composite id from list_inbox — '<source>:<platform id>'. A bare platform id is refused: replying to the wrong network as the brand cannot be taken back. |
| text | string | yes | The reply, exactly as it should appear publicly. |
Probe what this Hermoso account can do RIGHT NOW: available image/video model ids + their credit costs (estimates: a render is charged its actual cost), aspect ratios, video durations, the recipe ids, and the canEdit/canAvatar flags. Call it when you need a specific model id, a credit cost, or a capability you are not sure of. Read-only, free.
No parameter schema on file.
Return the credit balance, recent priced calls with their credits, and costModel (the one-sentence rule of what costs credits). THE RULE: only AI model runs and Ad Spy research spend credits; publishing, scheduling, ads management, analytics, comments, DMs and connectors cost no credits on any plan (X is the single per-call exception; xCost gives its exact credits per post, with and without a link
No parameter schema on file.
Out of credits? Top up with a credit PACK. Call with no argument to list the available packs (id · credits · price). If the account has a saved card and you have billing-admin rights, calling with pack quotes the exact charge and calling again with confirm:true AND the quote's quote_token charges the saved card instantly (same one-click top-up as the app — no redirect). If there's no saved card
| Parameter | Type | Required | Notes |
|---|---|---|---|
| pack | string | no | the pack id to buy (e.g. pack-2k) — omit to list the available packs first |
| confirm | boolean | no | set true to actually charge the saved card for pack (required for the one-click charge; ignored on the checkout-link path) |
| quote_token | string | no | the quoteToken returned by the quote step — REQUIRED (with confirm:true) to charge; it binds the exact pack + price you quoted (10-minute validity) and makes a retried confirm idempotent |
Report a bug in Hermoso to the team. Use this when something in Hermoso genuinely misbehaves — a tool errors unexpectedly, returns a wrong or malformed result, a render comes back broken, or documented behaviour doesn't match what happened. Include what you were trying to do, the exact tool call and arguments, and what came back. Do NOT use it for out-of-credits, a policy refusal, or a missing cap
| Parameter | Type | Required | Notes |
|---|---|---|---|
| summary | string | yes | one-line summary of the bug |
| details | string | yes | what you were doing, the tool + arguments you called, what you expected, and what actually happened (paste the exact error) |
| severity | string | no | high = blocks the task or loses paid work; medium = wrong output but workable; low = cosmetic |
Ask the Hermoso team for a capability that doesn't exist yet. Use this when you need something Hermoso genuinely can't do — an unsupported platform or channel, a missing model, an export format, a tool that would have completed the user's task but isn't available. Say what the user was trying to achieve, not just the feature name — the use case is what gets built. Free, no credits.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| summary | string | yes | one line: the capability you need |
| details | string | yes | what the user was actually trying to achieve, why the current tools couldn't do it, and what you'd expect the capability to do |
Show this account's billing at a glance: current plan (quote plan.priceUsd per plan.period), credit balance, whether auto-reload is on, whether a card is on file, and whether YOU (this key) have ADMIN rights to change billing. Read-only, free. Call it before upgrade_plan / set_auto_reload to know what's possible — members have read-only billing. IN A SHARED TEAM WORKSPACE A MEMBER SEES THE PLAN AN
No parameter schema on file.
Change the SUBSCRIPTION plan (admin only). No argument lists the monthly plans. Basic ($9.99) is the posting plan: unlimited posting and Autopilot with NO monthly credits (it includes the same 250+ free credits as the Free plan, monthly credits start on Starter, and Autopilot spends credits). Starter, Pro and Studio include monthly credits and no markup on models. Every paid plan posts without a l
| Parameter | Type | Required | Notes |
|---|---|---|---|
| plan | string | no | the plan id to move to (e.g. pro) — omit to list the available plans first |
| period | string | no | monthly only ('yr' buys monthly) |
| confirm | boolean | no | true (with quote_token) to make a quoted upgrade |
| quote_token | string | no | the quoteToken from the quote step |
Turn automatic credit reloads on or off (admin only): when the balance drops below a threshold, the card on file is charged for a top-up pack — SERVER-SIDE, even with no app open. Requires a saved card, added once in the app at first checkout/top-up; if there's none the tool tells you exactly where to add it. After that one-time card setup, agents can manage auto-reload, top-ups and plan links ful
| Parameter | Type | Required | Notes |
|---|---|---|---|
| enabled | boolean | yes | true to turn auto-reload on, false to turn it off |
| thresholdCredits | integer | no | reload when the balance drops below this many credits |
| reloadCredits | integer | no | how many credits to add each reload — must match a credit pack size (see buy_credits) |
List every profile on this account (id + name; a profile is a brand, a creator or a personal workspace) and which one this connection acts on, PLUS any profile another account shared with you. Call this, then use_brand to switch; a SHARED profile switches the same way (pass its name or the profile id printed here). If a profile looks empty when the app shows it full, you are acting on a different
No parameter schema on file.
Pin which profile this connection acts on. Pass the profile id or exact name from list_brands. Works for your own profiles AND one another account SHARED with you (pass its name or the profile id list_brands prints); access is verified against your real membership before it is pinned. Persists for this API key until changed, on every surface (hosted connector included — no environment variables, n
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | yes | profile id (e.g. default / p_xxx), its exact name from list_brands, or the profile id of one shared with you |
Add a NEW profile (a brand, client, creator or personal workspace) and switch to it. Each has its OWN brand details, memory, swipefile, Library, avatars, skills, playbooks and connectors; nothing leaks between them. Use draft_brand to FILL it and update_brand to edit it. Re-running with the same name returns the existing profile instead of a duplicate. Creating it is free. Drafting a NEW brand int
| Parameter | Type | Required | Notes |
|---|---|---|---|
| name | string | yes | the name for the new profile (a brand, client or creator name) |
| activate | boolean | no | switch this connection to the new profile (default true); everything you do next scopes to it |
PERMANENTLY delete a profile and EVERYTHING in it — brand details, memory, swipefile, Library creations, generated assets, avatars, skills, playbooks, chats — and disconnect its connected accounts. Irreversible, and it applies to everyone the workspace is shared with. Call it WITHOUT confirm first: it reports exactly what that workspace holds. Show the user that inventory verbatim, get an unambigu
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | yes | profile id or exact name from list_brands |
| confirm | boolean | no | REQUIRED true — this destroys the whole profile and cannot be undone |
| confirmName | string | no | the workspace's EXACT name, required when it is not empty — copy it from the inventory this tool returned, after the user has agreed to it |
| confirmConnectors | number | no | the number of connected accounts the inventory reported, required when there is at least one — the user must specifically agree to losing them, because reconnecting each needs a browser and no agent c |
Organic performance for the brand’s connected Facebook Page — views and unique reach (page_media_view / page_total_media_view_unique, Meta’s own replacements for the impressions family it retired), post engagements, video views, daily follows, plus follower and Page-like counts — with the linked Instagram account’s headline numbers alongside. This is ORGANIC reach. Any metric Meta returns no value
| Parameter | Type | Required | Notes |
|---|---|---|---|
| pageId | string | no | Page id — omit when the profile has exactly one Page connected |
| period | string | no | window (default week) |
Performance for a single organic post — on Facebook views/reach (post_media_view, post_total_media_view_unique — Meta’s own replacements for the retired impressions family), clicks, reactions and video watch time; on Instagram views, reach, likes, comments, saves, shares, total interactions and (where the media type has them) follows, profile visits, story navigation and reel watch time. Use it to
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| postId | string | yes | post/media id returned by post_to_meta |
| target | string | no | which metric set to ask for; an Instagram media id is detected and read as Instagram either way (default: Facebook for a Facebook id) |
| account | string | no | which Instagram account — an @handle or id from list_connector_accounts("instagram"). Needed when several are linked, and the way an Instagram Login (standalone) account is reached; omit for the one P |
| pageId | string | no | Page id — omit when only one Page is connected |
ACCOUNT-level performance for the brand’s connected Instagram Business account — views, reach, accounts engaged, total interactions, likes, comments, shares, saves, profile link taps, replies, reposts and follows/unfollows — plus the AUDIENCE DEMOGRAPHICS (follower_demographics and engaged_audience_demographics, broken down by age, city, country or gender), which is the read that says WHO the cont
| Parameter | Type | Required | Notes |
|---|---|---|---|
| metrics | array | no | account metrics (default: views, reach, accounts_engaged, total_interactions, likes, comments, shares, saves, profile_links_taps). Add follower_demographics or engaged_audience_demographics for the au |
| breakdown | array | no | contact_button_type / follow_type / media_product_type for account metrics; age / city / country / gender for the demographic metrics (exactly one) |
| timeframe | string | no | window for the demographic metrics only (default this_month = the last 30 days). engaged_audience_demographics takes ONLY this_month or this_week — Meta retired the other four for it from API v20; fol |
| period | string | no | aggregation for reach, the one time-series metric (default day) |
| since | string | no | YYYY-MM-DD window start |
| until | string | no | YYYY-MM-DD window end |
| account | string | no | which Instagram account — an @handle or id from list_connector_accounts("instagram"). Needed when several are linked, and the way an Instagram Login (standalone) account is reached; omit for the one P |
| pageId | string | no | Facebook Page id the Instagram account is linked to — omit when only one Page is connected |
The connected Instagram Business account’s own recent media — id, caption, media type (feed / reel / story-era), permalink, timestamp, like and comment counts. This is where the media id every other Instagram tool needs comes from: resolve “my latest reel” yourself instead of asking the user for a link, then pass the id to meta_post_insights. Read-only, 0 credits.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| limit | number | no | how many (1–50, default 15) |
| account | string | no | which Instagram account — an @handle or id from list_connector_accounts("instagram"). Needed when several are linked, and the way an Instagram Login (standalone) account is reached; omit for the one P |
| pageId | string | no | Facebook Page id — omit when only one Page is connected |
The Stories live right now on the connected Instagram accounts and Facebook Pages: id, caption (Instagram), posted time, hours left and permalink, and on Instagram insights: reach, views, replies, link clicks and navigation (forward, back, next story, exit). Use it to check that a post or reel really went to Stories, or to match a story id against what is live. includeEnded also returns ended,
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| channel | string | no | default all |
| account | string | no | one Instagram account: an @handle or id |
| pageId | string | no | one Facebook Page, and its linked Instagram |
| limit | number | no | rows, newest first (1-100, default 25) |
| includeEnded | boolean | no | also list Stories that ended, with their kept numbers |
| days | number | no | how far back includeEnded goes (1-400, default 30) |
An Instagram Story’s numbers: reach, views, replies, shares, link clicks, profile visits, follows, and navigation (forward, back, next story, exit). Meta keeps them for 24 hours, so Hermoso reads every live Story on a schedule, including ones posted from the phone, and they stay readable here after the Story ends. Pass storyId for one, or nothing for the recent ones. state says what is known: nu
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| storyId | string | no | one Story id, from list_meta_stories |
| account | string | no | one Instagram account: an @handle or id |
| pageId | string | no | one Facebook Page, for its linked Instagram |
| days | number | no | how far back (1-400, default 30) |
| limit | number | no | rows, newest first (1-100, default 25) |
What can be known about one story id, usually from a story_reply or story_mention item in list_inbox: its caption, posted time and permalink while it is live, or that it has expired. state is live, readable, not_a_story, expired, not_found or unknown. Meta serves a Story for 24 hours and answers an expired one with the same error as a wrong id, so pass repliedAt (the item’s at) and the url so
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| storyId | string | no | the story id from the inbox item, or a media id |
| url | string | no | the story url from the same inbox item |
| repliedAt | string | no | when the reply arrived, the item’s at; a reply older than 24 hours means the Story has expired |
| account | string | no | one Instagram account: an @handle or id |
| pageId | string | no | one Facebook Page |
Did the collab invites on an Instagram post get accepted? Reports every collaborator on one of the brand’s OWN Instagram posts with the status Instagram actually holds for it — Accepted (the post is live on their profile too, and its reach now includes their followers), Pending (invited, sitting in their notifications, NOT yet on their profile) or Declined. This is the tool for “did @creator accep
| Parameter | Type | Required | Notes |
|---|---|---|---|
| mediaId | string | yes | the Instagram media id — post_to_meta returns it as postId, list_instagram_media lists the account’s own posts |
| pageId | string | no | Facebook Page id — omit when only one Page is connected |
Audio the brand may legally put under a Reel — music or original sound — with title, artist, length, whether it is eligible for ads, a preview link and a download link. Omit the query for what is TRENDING right now; pass one to search. Everything returned is audio Instagram has authorized for third-party use. Instagram offers this library only to an account linked to a Facebook Page and connected
| Parameter | Type | Required | Notes |
|---|---|---|---|
| audioType | string | no | default music |
| query | string | no | omit for trending audio |
| limit | number | no | 1–50, default 15 |
| account | string | no | which Instagram account — an @handle or id from list_connector_accounts("instagram"); omit for the one Page-linked account |
| pageId | string | no | Facebook Page id — omit when only one Page is connected |
Posts where a creator tagged this Instagram account as a COLLABORATOR and is waiting for an answer. The post appears on the brand’s profile only once the brand accepts, and Instagram sends no notification here, so this list is the only way to see them. Answer each with respond_instagram_collab_invite. Read-only, free. Instagram allows 300 reads per account per day.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| limit | number | no | 1–100, default 25 |
| after | string | no | cursor from a previous page |
| account | string | no | which Instagram account — @handle or id; omit for the one Page-linked account |
| pageId | string | no | Facebook Page id — omit when only one Page is connected |
Answer a collab invite from list_instagram_collab_invites. accept:true makes this account a co-author and the creator’s post appears on its profile; accept:false declines. accept is REQUIRED — never guess which way the user wants it. The reply is read back from Instagram (the invite leaves the pending list), not taken from the vendor’s 200. Free. Instagram allows 50 answers per account per day.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| mediaId | string | yes | the media id from list_instagram_collab_invites |
| accept | boolean | yes | true = accept and co-author the post; false = decline |
| account | string | no | which Instagram account — @handle or id; omit for the one Page-linked account |
| pageId | string | no | Facebook Page id — omit when only one Page is connected |
Every collaborative post this Instagram account is a co-author on — who posted it, and the COMBINED engagement across all co-authors (total likes and comments, plus saves, shares and reposts where Instagram provides them). Read-only, free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| limit | number | no | 1–100, default 25 |
| after | string | no | cursor from a previous page |
| account | string | no | which Instagram account — @handle or id; omit for the one Page-linked account |
| pageId | string | no | Facebook Page id — omit when only one Page is connected |
Read the comments under a Facebook Page post or Instagram media object — customer questions, objections and the exact language real people use about the product. Good raw material for ad copy, and the first step before replying or moderating. REPLIES: a reply is a comment ON a comment, and its id exists only under its PARENT — it is never returned by the post. Each row says how many replies it has
| Parameter | Type | Required | Notes |
|---|---|---|---|
| postId | string | yes | post/media id — or a COMMENT id, which returns that comment’s replies |
| account | string | no | which Instagram account — an @handle or id from list_connector_accounts("instagram"). Needed when several are linked, and the way an Instagram Login (standalone) account is reached; omit for the one P |
| pageId | string | no | Page id — omit when only one Page is connected |
| limit | number | no | how many comments (1–50, default 25) |
| cursor | string | no | the cursor from a previous call. A post with more comments than one page comes back with hasMore + a truncationNote — counts or sentiment drawn from ONE page describe a sample, not the conversation. |
Leave a NEW top-level comment on one of the brand’s own posts — a Facebook Page post OR an Instagram post/Reel — as the brand. This is the "link in the first comment" tactic (Facebook suppresses reach on a post with an outbound link in the body, and an Instagram caption link is not clickable), and it is also how you add a pinned clarification, an offer code or a correction without editing the post
| Parameter | Type | Required | Notes |
|---|---|---|---|
| postId | string | yes | the Facebook Page post id or Instagram media id — from list_meta_posts, or returned by post_to_meta |
| message | string | no | the comment text. Required unless attachmentUrl is given. |
| attachmentUrl | string | no | FACEBOOK ONLY — optional public image URL to post as the comment’s attachment (refused on Instagram) |
| pageId | string | no | Page id — omit when only one Page is connected |
| account | string | no | INSTAGRAM — which account, an @handle or id from list_connector_accounts("instagram"); needed for an Instagram Login account or when several are linked |
Like (or unlike) an Instagram post, Reel, comment or reply AS the brand’s Instagram account — Meta’s Like Media and Comments API (April 2026). The cheapest engagement a brand does: like the good comments on your own posts and the posts you are tagged in. Pass exactly one of mediaId or commentId; undo:true unlikes. Stories and private accounts cannot be liked. Needs the Meta (Facebook Login) connec
| Parameter | Type | Required | Notes |
|---|---|---|---|
| mediaId | string | no | an IG media id (post or Reel), from list_instagram_media |
| commentId | string | no | an IG comment or reply id, from list_meta_comments |
| undo | boolean | no | true to unlike |
| pageId | string | no | Facebook Page id when the brand's Instagram is linked to a Page; from list_meta_pages |
| account | string | no | direct Instagram account (@handle or id) when several are connected; from list_connector_accounts |
SEARCH INSTAGRAM'S CREATOR MARKETPLACE: Meta's own directory of creators who partner with brands, searched AS the brand's Instagram account, so the recommendations are personalised to it. Use it when the user wants creators with first-party data (followers, audience, badges such as Partnership ads / Branded content / Strong hooks / Responsive, marketplace email) or Meta's recommendations. Search b
| Parameter | Type | Required | Notes |
|---|---|---|---|
| query | string | no | keywords, e.g. "skincare" or "trail running" |
| username | string | no | one creator handle; no other filter allowed with it |
| similarTo | array | no | up to 5 creator handles to find look-alikes of |
| recommendation | string | no | Meta's recommendation ranking for the results; omit for a plain search |
| countries | array | no | 2-letter ISO codes |
| states | array | no | state or region codes; needs exactly one country |
| minFollowers | number | no | 0, 10000, 25000, 50000, 75000, 100000, 250000 or 1000000 |
| maxFollowers | number | no | 10000, 25000, 50000, 75000, 100000, 250000 or 1000000 |
| ageBucket | string | no | creator age bucket filter, e.g. 25_to_34 |
| gender | string | no | creator gender filter: male or female |
| interests | array | no | up to 5 of Meta’s categories, e.g. BEAUTY, FASHION, FITNESS_AND_WORKOUTS, FOOD_AND_DRINK, TRAVEL_AND_LEISURE_ACTIVITIES |
| minEngaged | number | no | 0, 2000, 10000, 50000 or 100000 |
| maxEngaged | number | no | 2000, 10000, 50000 or 100000 |
| audienceCountries | array | no | 2-letter ISO codes of countries the creator's audience must match |
| audienceStates | array | no | state or region codes the creator's audience must match |
| audienceAgeBucket | string | no | age bucket of the creator's audience, e.g. 25_to_34 |
| audienceGender | string | no | gender of the creator's audience: male or female |
| audienceDevices | array | no | device filter for the creator's audience: ios and/or android |
| customAudienceId | string | no | from list_instagram_marketplace_audiences |
| reelsInteractionRate | number | no | minimum reels interaction rate, as a plain number |
ONE CREATOR FROM INSTAGRAM'S CREATOR MARKETPLACE, IN DEPTH: Meta's first-party insights (total followers, reach, engaged accounts, reels interaction rate, reels hook rate), audience demographics (engaged accounts by gender, age, top countries, top cities), the brands they partnered with in the past year, and their recent posts, branded-content posts and partnership ads with likes, comments, views
| Parameter | Type | Required | Notes |
|---|---|---|---|
| username | string | yes | the creator’s Instagram handle |
| demographics | boolean | no | default true |
| media | boolean | no | default true |
| pageId | string | no | Facebook Page id linked to the brand's Instagram; from list_meta_pages |
| account | string | no | direct Instagram account (@handle or id) when several are connected; from list_connector_accounts |
The brand's custom audiences that Instagram's creator marketplace accepts as a creator filter (Meta's creator_marketplace_brand_info). Pass an id from here as customAudienceId to find_instagram_marketplace_creators to find creators whose followers overlap that audience. An agency passes businessId (its Business portfolio id) to see audiences from client ad accounts. Needs the Meta connector, ads_m
| Parameter | Type | Required | Notes |
|---|---|---|---|
| businessId | string | no | agency Business portfolio id |
| pageId | string | no | Facebook Page id linked to the brand's Instagram; from list_meta_pages |
| account | string | no | direct Instagram account (@handle or id) when several are connected; from list_connector_accounts |
Post a public reply to a comment on the brand’s Facebook or Instagram post. This is PUBLIC and posted as the brand — show the user the exact wording and get their go-ahead first. With private:true it instead sends ONE private message to the commenter (Messenger / Instagram Direct) — once per comment, within 7 days.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| private | boolean | no | true = send ONE PRIVATE message to the person who left the comment (Messenger / Instagram Direct) instead of a public reply — the way to answer "price?" or "DM me". Meta allows one per comment, within |
| commentId | string | yes | comment id from list_meta_comments |
| message | string | yes | reply text |
| account | string | no | which Instagram account — an @handle or id from list_connector_accounts("instagram"). Needed when several are linked, and the way an Instagram Login (standalone) account is reached; omit for the one P |
| pageId | string | no | Page id — omit when only one Page is connected |
Moderate a comment on the brand’s Facebook or Instagram post. Hide is the default and can be undone. Deleting is PERMANENT and requires confirm:true after the user has agreed.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| commentId | string | yes | comment id from list_meta_comments |
| action | string | no | default hide |
| confirm | boolean | no | required (true) only for delete |
| account | string | no | which Instagram account — an @handle or id from list_connector_accounts("instagram"). Needed when several are linked, and the way an Instagram Login (standalone) account is reached; omit for the one P |
| pageId | string | no | Page id — omit when only one Page is connected |
List recent posts on the brand’s connected Threads account (id, text, media, permalink, timestamp). Use it to find a post id for threads_insights, list_threads_replies, reply_to_thread or delete_thread.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| limit | number | no | how many posts (1–50, default 15) |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Performance for ONE Threads post (views, likes, replies, reposts, quotes, shares) when postId is given, or for the whole ACCOUNT when it is omitted — views, likes, replies, reposts, quotes, LINK CLICKS, follower count, and follower_demographics broken down by country, city, age or gender. Note the two metric sets differ: "clicks" exists only at account level and "shares" only on a single post, and
| Parameter | Type | Required | Notes |
|---|---|---|---|
| postId | string | no | post id from list_threads_posts — omit for account-level insights |
| metrics | array | no | account metrics: views, likes, replies, reposts, quotes, clicks, followers_count, follower_demographics |
| breakdown | array | no | country / city / age / gender — required by follower_demographics, exactly one |
| since | string | no | YYYY-MM-DD window start (account scope) |
| until | string | no | YYYY-MM-DD window end (account scope) |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Read the replies on a Threads post. Set conversation:true to walk the entire thread rather than only direct replies. Use before reply_to_thread so you answer with the actual conversation in view.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| postId | string | yes | post id from list_threads_posts |
| conversation | boolean | no | true = the whole thread, not just direct replies |
| limit | number | no | how many replies (1–50, default 25) |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Post a reply to a Threads post — the brand’s own or someone else’s. This PUBLISHES publicly under the brand’s account, so show the user the exact wording and get their go-ahead first.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| replyToId | string | yes | the post id being replied to |
| text | string | yes | reply text (max 500 characters) |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Hide a reply on the brand’s Threads post (or unhide it with hide:false) — for spam and abuse moderation.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| replyId | string | yes | reply id from list_threads_replies |
| hide | boolean | no | false to UNHIDE (default true) |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
PERMANENTLY delete one of the brand’s Threads posts. IRREVERSIBLE — Threads has no undelete. Call it WITHOUT confirm first: nothing is deleted, and it answers with the post’s real text plus its views, likes, replies and reposts read back from Threads. Show the user that, get an unambiguous yes, then call again with confirm:true — plus confirmName (the post’s exact text as it was reported) once any
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| postId | string | yes | post id from list_threads_posts |
| confirm | boolean | no | REQUIRED true — deletion is permanent; only set it after the user has explicitly agreed |
| confirmName | string | no | the post’s exact text as the unconfirmed call reported it — required once it has any likes, replies or reposts |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Repost an existing Threads post to the brand’s own Threads profile — the Threads equivalent of a retweet. It is how a brand amplifies a customer’s post, a mention, or one of its own older threads without copying the text, and there was previously no way to do it. Works on any Threads post id: list_threads_posts, list_threads_mentions and search_threads_keyword all return them. This creates a NEW p
| Parameter | Type | Required | Notes |
|---|---|---|---|
| postId | string | yes | the Threads post id to repost |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
How much of the brand’s Threads quota is left right now — posts (250 per rolling 24 hours), replies (1,000), DELETIONS (100) and location searches (500) — each as used, total and REMAINING. Check it before any bulk operation, and read it the moment Threads starts refusing: a quota refusal is otherwise indistinguishable from a broken connection or a missing permission, and reconnecting cannot fix i
| Parameter | Type | Required | Notes |
|---|---|---|---|
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Posts where someone MENTIONED the brand on Threads — anywhere, not just under your own posts. Use list_threads_replies instead when you want the conversation under one specific post.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| limit | number | no | how many mentions (1–50, default 25) |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Search PUBLIC Threads posts for a keyword or topic — competitor listening, finding what people say about a product, or sourcing real customer language for ad copy.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| searchMode | string | no | KEYWORD (default) or TAG — TAG searches a TOPIC TAG (what is being said under #skincare) |
| mediaType | string | no | TEXT, IMAGE or VIDEO — only that kind of post |
| since | string | no | only posts on/after this date (YYYY-MM-DD, ISO or unix seconds; Threads search reaches back to 2023-07-05) |
| until | string | no | only posts on/before this date |
| limit | integer | no | how many results (1–100, default 25) |
| authorUsername | string | no | only posts by this Threads account (exact username, no @) — 'what has @competitor said about X' |
| q | string | yes | keyword or phrase |
| searchType | string | no | TOP (default) or RECENT |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
List the Facebook Pages (with any linked Instagram business account) and ad accounts on the connected Meta account. Requires the user to have connected Meta (Settings > Connectors > Meta); returns a connect hint if not.
No parameter schema on file.
Persist an ARBITRARY user file (image, video, audio, PDF or document, up to 150MB) into Hermoso and get back a durable public URL that EVERY publish, schedule and ad-build tool accepts. Kept in the Library. THIS IS THE BRING-YOUR-OWN-CREATIVE PATH: it is for files that have NOTHING to do with a Hermoso render (media on the user's desktop, an agency's finished ad, a photo they shot), and it means y
| Parameter | Type | Required | Notes |
|---|---|---|---|
| getUploadUrl | boolean | no | ASK FOR A ONE-TIME UPLOAD URL instead of uploading now — use this whenever the file is on the user’s machine and you can run a shell or an HTTP request. Returns a uploadUrl you PUT the raw bytes to (a |
| url | string | no | a PUBLIC http(s) URL Hermoso fetches server-side (private/internal addresses are refused, and every redirect hop is re-checked). Works on every surface including the hosted connector, and the bytes ne |
| dataUri | string | no | base64 data: URI of the file bytes (data:<mime>;base64,<…>) — bytes travel over this connection, so keep it small |
| name | string | no | original file name — helps pick the right extension |
Search Threads’ public place index by name (or by latitude+longitude) and get location ids. Use this when the brand has a PHYSICAL location — a restaurant, salon, gym, store — so the post can be geotagged to it. Pass the chosen id as post_to_meta(locationId) with target:"threads".
| Parameter | Type | Required | Notes |
|---|---|---|---|
| q | string | no | place name to search, e.g. "Osteria Francescana" |
| latitude | number | no | latitude (use with longitude to search near a point) |
| longitude | number | no | longitude |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Read ANY public Threads account by handle — name, bio, avatar, verified badge, follower count, and its lifetime likes / quotes / reposts / views. This is competitor and creator research on Meta’s own data rather than a scraper: read a rival’s positioning straight off their profile instead of guessing it, then read what they are posting with list_threads_profile_posts and draft the brand’s answer.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| username | string | yes | the Threads handle — "nike", "@nike", or a threads.net profile link |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
The recent PUBLIC posts of any Threads account. AN EMPTY LIST IS NEVER PROOF THEY HAVE NOT POSTED — Meta returns nothing for a private account, for any account under 100 followers, and when the Threads connection predates the threads_profile_discovery permission (approved for the app 2026-09-05; reconnect Threads to grant it); the note says which of those applies. Read-only, 0 credits. Needs Thr
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| username | string | yes | the Threads handle — "nike", "@nike", or a threads.net profile link |
| limit | number | no | how many posts (1–50, default 25) |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Read the PUBLIC Instagram posts carrying a hashtag — the caption, the media, the permalink and the like and comment counts — on the brand’s own Meta credentials rather than a scraper. This is category and competitor listening: find how a market actually talks about a product, then USE it. TWO EDGES AND THEY ANSWER DIFFERENT QUESTIONS: edge:"recent" (default) is what has been posted in the LAST 24
| Parameter | Type | Required | Notes |
|---|---|---|---|
| tag | string | yes | the hashtag — "skincare", "#skincare" or an instagram.com/explore/tags/… link all work. Instagram matches it EXACTLY and tags carry no spaces or punctuation. |
| edge | string | no | "recent" (default) = posted in the last 24 hours; "top" = the best-performing posts, with no time limit. Use "top" whenever "recent" comes back empty. |
| limit | number | no | how many posts (1–50, default 10). Instagram bounds a page by how much DATA it holds, not by how many posts, so a big ask is fetched in several smaller pages automatically and may still come back shor |
| pageId | string | no | Facebook Page id — omit when only one Page is connected. Its linked Instagram account is what Meta charges the hashtag budget against. |
The WhatsApp Business Accounts SHARED WITH THIS PROFILE and the phone numbers registered on each — the ids every other WhatsApp tool needs, plus each number’s QUALITY RATING, which is what decides how many messages Meta will let it send. Start here. A WABA with no number cannot send anything and the reply says so: Hermoso does not register or verify numbers, that is WhatsApp Manager. A business po
No parameter schema on file.
The message templates on one WhatsApp Business Account with the REVIEW STATUS Meta actually holds for each. THE STATUS IS THE POINT: an APPROVED template is the only thing WhatsApp accepts outside the 24-hour customer-service window, and Meta’s own refusal for anything else says "the template does not exist in the specified language or the template has not been approved" — two completely different
| Parameter | Type | Required | Notes |
|---|---|---|---|
| wabaId | string | no | the WhatsApp Business Account id, from list_whatsapp_accounts. Optional when exactly one account is shared with this profile; required when several are, and refused by name for one that is not shared |
| name | string | no | filter to one template name (Meta matches it exactly) |
| language | string | no | filter to one language code, e.g. en_US |
| status | string | no | filter to one review status, e.g. APPROVED or PENDING |
| limit | number | no | how many (1–200, default 50) |
Submit a new WhatsApp message template for Meta’s review. This is the ONLY way a business can start a WhatsApp conversation: outside the 24-hour window that opens when a customer messages first, WhatsApp accepts approved templates and nothing else. CATEGORY IS NOT COSMETIC — MARKETING (anything promotional), UTILITY (a transactional follow-up to something the customer did) or AUTHENTICATION (a one
| Parameter | Type | Required | Notes |
|---|---|---|---|
| wabaId | string | no | the WhatsApp Business Account id, from list_whatsapp_accounts. Optional when exactly one account is shared with this profile; required when several are, and refused by name for one that is not shared |
| name | string | yes | lowercase letters, digits and underscores only, e.g. "order_shipped" (max 512) |
| language | string | yes | the language code, e.g. en_US, es_MX, pt_BR — Meta treats each as its own template |
| category | string | yes | MARKETING = promotional, UTILITY = transactional follow-up, AUTHENTICATION = one-time passcode. It sets the price and the review policy. |
| components | array | yes | Meta’s component array. A BODY is the only REQUIRED one: [{ type: "BODY", text: "Your order {{1}} has shipped." }]. Optional HEADER (text max 60), FOOTER (max 60) and BUTTONS (up to 10 in total). Body |
| parameterFormat | string | no | POSITIONAL (default, {{1}}) or NAMED |
PERMANENTLY delete a WhatsApp message template. META DELETES EVERY LANGUAGE OF A NAME AT ONCE, which is a wider blast radius than most callers picture, so call it WITHOUT confirm first: nothing is deleted and it reports exactly how many templates and which languages would go. Then show the user that, get an unambiguous yes, and call again with confirm:true AND confirmName set to the template’s exa
| Parameter | Type | Required | Notes |
|---|---|---|---|
| wabaId | string | no | the WhatsApp Business Account id. Optional when exactly one is shared with this profile; an account that is not shared is refused by name |
| name | string | yes | the template name (Meta matches it exactly) |
| confirm | boolean | no | REQUIRED true to actually delete — deletion is permanent and covers every language |
| confirmName | string | no | the template’s exact name, echoed back — required alongside confirm |
Send a WhatsApp message from one of the brand’s registered numbers to a customer. TWO KINDS, and which one is legal depends on time: pass template (an APPROVED template name) to START a conversation — the only thing WhatsApp accepts outside the 24-hour customer-service window — or text for a free-form reply INSIDE that window, which opens only when the customer messages the business first. Her
| Parameter | Type | Required | Notes |
|---|---|---|---|
| phoneNumberId | string | yes | WHICH NUMBER IT SENDS FROM — from list_whatsapp_accounts (a WABA can have several) |
| to | string | yes | the recipient in full international form, e.g. +14165550142 — or, for someone who reached the business by WhatsApp username and shared no number, their business-scoped user id exactly as received (e.g |
| template | string | no | an APPROVED template name — required to start a conversation outside the 24-hour window |
| language | string | no | which language of that template, e.g. en_US. Omitted, Hermoso uses the first one it finds and reports which. |
| wabaId | string | no | the account the template’s review status is read from. Optional — it defaults to the account that owns phoneNumberId, and a wabaId naming a DIFFERENT account is refused rather than silently preferred |
| components | array | no | the template’s variable values, in Meta’s component form. The count must match the {{1}}, {{2}} placeholders exactly. Hermoso checks this against the template’s real components before dispatching and |
| text | string | no | a free-form message — only lands INSIDE the 24-hour window |
| confirm | boolean | no | REQUIRED true — this sends a real message to a real phone and Meta bills the business for it |
Send ONE approved WhatsApp template to MANY recipients — a customer list, a segment, a launch announcement. TEMPLATES ONLY: WhatsApp accepts free-form text only inside the 24-hour customer-service window that opens when THAT person messages the business, so a free-form broadcast is a separate bet on a separate window per person and Meta refuses each closed one. META HAS NO BULK ENDPOINT, so this i
| Parameter | Type | Required | Notes |
|---|---|---|---|
| jobId | string | no | READ MODE — pass a broadcast’s job id (and nothing else) to get its status and per-recipient outcome instead of starting a new one |
| phoneNumberId | string | no | WHICH NUMBER IT SENDS FROM — from list_whatsapp_accounts (a WABA can have several). Required to SEND; omit only when reading a broadcast back with jobId |
| template | string | no | an APPROVED template name. Required to SEND — a broadcast cannot be free-form text; omit only when reading a broadcast back with jobId |
| language | string | no | which language of that template, e.g. en_US. Omitted, Hermoso uses the first one it finds and reports which. |
| wabaId | string | no | the account the template is read from. Optional — it defaults to the account that owns phoneNumberId, and one naming a DIFFERENT account is refused |
| recipients | array | no | the list: either phone numbers in full international form ["+14165550142", …] (or business-scoped user ids, e.g. US.13491208655302741918, for people who reached you by username), or objects { to, comp |
| components | array | no | template values SHARED by every recipient, in Meta’s component form. A per-recipient components overrides it. The count must match the template’s {{1}}, {{2}} placeholders — Hermoso checks that agai |
| skipInvalid | boolean | no | drop unusable numbers instead of refusing the whole list. They are still named in the result — nothing is ever silently dropped |
| dryRun | boolean | no | cost nothing, send nothing, queue nothing: report exactly who would be messaged, what Meta charges for it, and whether it fits the 24-hour messaging limit |
| confirm | boolean | no | REQUIRED true — this sends real messages to real phones and Meta bills the business for every one |
| confirmCount | number | no | REQUIRED — the exact number of DISTINCT valid recipients, echoed back. confirm:true alone cannot tell a 12-person test from a 2,000-person send |
Use this when the user wants to post now to Facebook, Instagram or Threads. Publish to a connected Facebook Page, its linked Instagram, OR the brand’s Threads account — text/link/image/VIDEO/CAROUSEL. A MULTI-SLIDE creative is a CAROUSEL, not several posts: pass the slides in order as imageUrls[] and they publish as ONE swipeable post (Instagram album, Threads carousel, Facebook multi-photo post).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no profile, or two, is R |
| hook | string | no | WHAT ANGLE this post is built on, recorded only at publish time. Omit it and this post never votes on which hook works. |
| subject | string | no | WHAT THIS POST IS ABOUT — product, feature, offer or theme (e.g. "winter coat", "free trial"). post_performance's second grouping axis: reuse the exact wording, as with hook. |
| recipe | string | no | the post's FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format |
| ideaId | string | no | short id of the content-plan idea this post came from |
| message | string | no | post text / caption |
| imageUrl | string | no | public https URL, a data: URI, or a Hermoso /generated path (upload_file gives you one for a local file); a URL, not a local file path; use upload_file for local files |
| videoUrl | string | no | public https URL, data: URI, or /generated path — FB video post / IG Reel; a URL, not a local file path; use upload_file for local files |
| productTags | array | no | INSTAGRAM SHOPPING — make the post SHOPPABLE by tagging products from the brand’s own catalog; tapping a tag opens the product’s price sheet inside Instagram. Instagram only. ON A PHOTO each tag is {p |
| imageUrls | array | no | CAROUSEL — an ORDERED list of image (and, where the channel allows, video) URLs published as ONE post the viewer swipes through. THIS IS NOT “post several” — it is a single post with several slides, w |
| idempotencyKey | string | no | SAFE RETRIES — any stable string: a repeat of the SAME publish within 24h returns the ORIGINAL post id instead of posting again (an identical publish is also auto-recognised for 10 minutes). On a time |
| allowDuplicate | boolean | no | post it even though an identical post was just made or attempted. Only pass this when the user genuinely wants the same thing posted twice, or when you have LOOKED at the account and confirmed a timed |
| async | boolean | no | publish in the BACKGROUND and return a job id to poll with get_job, instead of waiting. USE THIS FOR VIDEO: a Facebook or Instagram video publish routinely outlives an agent transport, and a timeout o |
| link | string | no | a URL to attach (FB text post only) |
| collaborators | array | no | INSTAGRAM COLLAB — up to 3 Instagram usernames invited to CO-AUTHOR this post. Once one accepts, the post appears on THEIR profile too, with both handles in the header and the likes and comments share |
| userTags | array | no | INSTAGRAM USER TAGS — tag people (public accounts) on the post; each is notified. Usernames, or { username, x, y } with x/y from 0 to 1 = where the tag sits on the IMAGE (a tag without a position sits |
| target | string | no | default facebook; instagram -> the Page’s linked IG; threads -> the brand’s connected Threads account |
| account | string | no | WHICH Instagram account when target is instagram and the brand has several — Page-linked and Instagram Login accounts alike; an @username or id from list_connector_accounts("instagram"). Several and n |
| scheduleAt | string | no | FACEBOOK ONLY — schedule instead of posting now. ISO timestamp (2026-08-01T09:00:00Z) or unix seconds; must be 10 minutes to 30 days ahead. Facebook holds the post and publishes it at that time, so no |
| locationId | string | no | TAG A PLACE. THREADS: a place id from search_threads_locations. A non-numeric value is refused rather than sent. |
Use this when the user wants to schedule a social media post for later, on one channel or several at once. Scheduling and publishing cost no credits on any Hermoso plan (X alone bills per call: hermoso_credits gives the exact credits per X post). THE FREE PLAN INCLUDES 10 POSTS A MONTH ON UP TO 3 CHANNELS (a post counts once, however many channels it goes to; counted in the UTC month the post goes
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no profile, or two, is R |
| hook | string | no | WHAT ANGLE this post is built on, recorded only at publish time. Omit it and this post never votes on which hook works. |
| subject | string | no | WHAT THIS POST IS ABOUT — product, feature, offer or theme (e.g. "winter coat", "free trial"). post_performance's second grouping axis: reuse the exact wording, as with hook. |
| recipe | string | no | the post's FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format |
| ideaId | string | no | short id of the content-plan idea this post came from |
| channels | array | yes | one or more channels to post to at that time |
| at | string | no | when to post: local wall clock, no offset (2026-10-30T10:00), read in timezone or the brand’s zone ON THAT DATE so a series survives a clock change; or UTC (…Z) / epoch ms. Future, at most 365 days |
| useQueue | boolean | no | instead of naming a minute, drop this into the brand’s POSTING QUEUE: Hermoso takes the earliest of its saved posting times that is still free (skipping any slot another queued post already holds). Th |
| timezone | string | no | IANA zone (e.g. "Africa/Cairo") a wall-clock at or the queue is read in; overrides the brand’s saved zone for this one post. Without either, UTC. |
| message | string | no | the caption/text used for every channel unless overridden in captions |
| captions | object | no | per-channel caption overrides, e.g. { "instagram": "…", "threads": "…" } — platforms want different lengths and hashtag conventions |
| accounts | object | no | WHICH accounts of a multi-account channel to post to, e.g. { "tiktok": ["@a", "@b"] } or { "tiktok": "all" } — one row per account at fire time, each with its own result. Omit for channels with one ac |
| imageUrl | string | no | a Hermoso render URL (a /generated path, or what upload_file returned), a data: URI, or a public https URL. NOTE: only Facebook/Instagram/Threads accept an arbitrary public URL — X, TikTok, YouTube, L |
| videoUrl | string | no | a Hermoso render URL (a /generated path, or what upload_file returned), a data: URI, or a public https URL — required for youtube, and for tiktok unless you pass an imageUrl (TikTok takes a photo post |
| imageUrls | array | no | CAROUSEL — an ORDERED list of image URLs to publish as ONE swipeable post on every channel that supports it (Instagram 2–20, Threads 2–20, Facebook, LinkedIn company Pages 2–20, Pinterest 2–5, TikTok |
| title | string | no | PINTEREST / YOUTUBE — the headline, max 100 characters. Pinterest shows it in search and under the pin; YouTube requires one. Leave it out and Hermoso derives one from that channel’s caption (first se |
| description | string | no | YOUTUBE: the video DESCRIPTION, max 5000 characters, carrying the links, the CTA and what YouTube search reads. Omit it and the caption is used. |
| tags | array | no | YOUTUBE — up to 30 search tags for the video (plain words, no #). |
| thumbnailUrl | string | no | YOUTUBE: the custom thumbnail, a Hermoso-hosted image. Omit it and a frame of the video is used; "auto" keeps YouTube’s pick. |
| paidProductPlacement | boolean | no | YOUTUBE: paid product placement (shows the disclosure) |
Use this when the user asks what is scheduled, what already posted, or whether a post went out. Show what is queued to post and what already went out. Each fired item reports PER-CHANNEL outcomes, so you can see that (say) Instagram published and TikTok failed on the same item rather than a single misleading verdict. Past items are rebuilt from published-post records, one row per channel; they are
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE to list — the id or exact name from list_brands. Needed when the post lives in a profile this connection is not pinned to: a post you can CREATE in a profile must be manageable there too |
| id | string | no | one post id from this list: returns that post in full, every caption and setting included |
| channel | string | no | only posts that include this channel, e.g. "pinterest" or "x" |
| upcoming | number | no | how many queued posts to list, soonest first (default 25, max 200) |
| fired | number | no | how many already-fired posts to list, most recent last (default 15, max 200) |
Change a post that is still QUEUED — move it to a different time, rewrite the caption, swap the media, add or drop a channel, or change which board / Page / company Page / listing it goes to. PASS ONLY WHAT CHANGES: an omitted field is left exactly as it was, and an explicit empty string CLEARS one (linkedinOrganizationId:"" moves a company-Page post back to the person’s own profile). The edited i
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE this post is in — id or exact name from list_brands; needed when it is not the profile this connection is pinned to. A name that matches no profile, or two, is REFUSED. |
| recipe | string | no | the post's FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format |
| ideaId | string | no | short id of the content-plan idea this post came from |
| id | string | yes | the scheduled post id from list_scheduled |
| at | string | no | the new time: local wall clock (2026-10-30T10:00, read in timezone or the brand’s zone on that date), UTC (…Z) or epoch ms. Future, at most 365 days out. |
| timezone | string | no | IANA zone for a wall-clock at |
| message | string | no | replace the caption used for every channel that has no override |
| captions | object | no | replaces the WHOLE per-channel caption map — send every override you want to keep, not just the new one |
| optimizeCopy | boolean | no | fit the shared caption to each channel’s own rules when you schedule it (the fitted caption is stored on the scheduled post, so what you scheduled is what publishes) wherever no per-channel caption wa |
| channels | array | no | replaces the channel list |
| imageUrl | string | no | swap the image; "" removes it |
| videoUrl | string | no | swap the video; "" removes it |
| imageUrls | array | no | replace the CAROUSEL slides, in order; an empty array [] drops the carousel and goes back to a single image. Omit to leave the slides exactly as they are. The edited item is re-checked against the sam |
| title | string | no | PINTEREST / YOUTUBE — replace the headline; "" clears it and goes back to deriving one from the caption |
| description | string | no | YOUTUBE — replace the video description; "" clears it and the caption is used. |
| tags | array | no | YOUTUBE — replaces the WHOLE tag list; an empty array [] clears the tags. |
| thumbnailUrl | string | no | YOUTUBE: replace the custom thumbnail; "" goes back to a frame of the video, "auto" to YouTube’s pick. |
| paidProductPlacement | boolean | no | YOUTUBE: paid product placement (shows the disclosure) — false removes it |
| brandPartner | string | no | YOUTUBE: sponsoring brand channel (UC… or @handle); "" removes it |
| replyControl | string | no | THREADS ONLY — who may reply. |
Remove a queued post before it goes out. Get the id from list_scheduled. Only works while it is still queued — something already published cannot be unsent (use manage_meta_post to delete a Facebook/Instagram post after the fact).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| id | string | yes | the scheduled post id from list_scheduled |
| brand | string | no | WHICH PROFILE this post is in — id or exact name from list_brands; needed when it is not the profile this connection is pinned to. A name that matches no profile, or two, is REFUSED. |
Send a post that FAILED again. A scheduled post fans out across its channels INDEPENDENTLY, so a failure is usually PARTIAL — LinkedIn 401s while Instagram published fine — and this re-fires ONLY the channels that did not succeed by default (list_scheduled reports them as retryable). It re-queues the same content as a NEW post that goes out RIGHT AWAY — the queue picks it up on its next pass, wi
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE this post is in — id or exact name from list_brands; needed when it is not the profile this connection is pinned to. A name that matches no profile, or two, is REFUSED. |
| id | string | yes | the scheduled post id from list_scheduled |
| channels | array | no | retry only these channels (default: every channel that did not publish) |
| at | string | no | hold the retry until a later time — ISO timestamp or epoch milliseconds. Leave it out to retry immediately, which is almost always what you want. A time you name here must be at least a minute from no |
| allowDuplicate | boolean | no | ONLY for a channel you have checked by hand and confirmed the post is genuinely NOT there. It bypasses the double-post protection and can publish a second public copy, so never set it to work around a |
| boardId | string | no | CORRECT THE BOARD on retry — the Pinterest board the Pin goes on, from list_pinterest_boards. A Pin aimed at a board Pinterest refuses fails the same way on every retry until this is changed. |
| chatId | string | no | CORRECT THE TELEGRAM DESTINATION on retry — the @username or numeric id of the chat. A post aimed at a chat the bot is not in fails every time it is retried until this changes. |
| whopExperienceId | string | no | WHOP — correct the forum or chat on retry (exp_…, from list_whop_destinations). |
| pageId | string | no | CORRECT THE PAGE on retry — which connected Facebook Page (and its linked Instagram/Threads) publishes, from list_meta_pages. |
| linkedinOrganizationId | string | no | CORRECT THE LINKEDIN AUTHOR on retry — the company Page id from list_linkedin_pages. Set it to an empty string to fall back to the personal profile. |
| locationId | string | no | CORRECT THE LISTING on retry — which Google Business Profile location, e.g. "locations/123" from list_business_locations. |
| message | string | no | CORRECT THE CAPTION on retry — use this when the original was refused for length or content. Anything not named here is copied from the original post. |
| captions | object | no | CORRECT ONE CHANNEL’S CAPTION on retry, e.g. { "x": "..." } when only that channel refused the text. |
Copy an existing scheduled or already-published post into a NEW queued post — the way to run a creative again, reuse a post that worked as the starting point for the next one, or re-send something after it went out. It copies the caption, media, per-channel captions, title, description, tags and the target board / Page / company Page / listing, and ANY of those can be overridden in the same call.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE this post is in — id or exact name from list_brands; needed when it is not the profile this connection is pinned to. A name that matches no profile, or two, is REFUSED. |
| id | string | yes | the post to copy, from list_scheduled |
| recipe | string | no | the post's FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format |
| ideaId | string | no | short id of the content-plan idea this post came from |
| at | string | no | when the copy goes out: local wall clock (2026-10-30T10:00), UTC or epoch ms (default: an hour from now) |
| useQueue | boolean | no | instead of naming a time, take the brand’s next free posting slot |
| timezone | string | no | IANA zone for a wall-clock at or the queue |
| channels | array | no | post the copy to these channels instead of the original’s |
| message | string | no | a different caption for the copy |
| captions | object | no | per-channel caption overrides for the copy |
| imageUrl | string | no | replacement image for the copy: a Hermoso render URL or an upload_file URL |
| videoUrl | string | no | replacement video for the copy: a Hermoso render URL or an upload_file URL |
| imageUrls | array | no | CAROUSEL — an ORDERED list of image URLs published as ONE swipeable post |
| title | string | no | Pinterest or YouTube headline for the copy, max 100 characters |
| link | string | no | destination URL for the copy (Pinterest, Facebook text post or Google Business post) |
| boardId | string | no | PINTEREST — the board for the copy (list_pinterest_boards) |
| chatId | string | no | TELEGRAM — which chat, group or channel the copy goes to (@username or numeric id) |
| whopExperienceId | string | no | WHOP — which forum or chat the copy goes to (exp_…, from list_whop_destinations). |
| linkedinOrganizationId | string | no | LINKEDIN — publish the copy as this company Page (list_linkedin_pages) |
| pageId | string | no | FACEBOOK / INSTAGRAM / THREADS — which connected Page (list_meta_pages) |
Show AUTOPILOT POSTING (autoposting, the posting refill) for this profile — "is autopilot on?", "what is waiting for review?": whether it is on, its mode (review = each batch of fresh posts waits as drafts for approval; auto = fresh posts are scheduled straight away), WHICH CHANNELS it posts to with each one’s posts per day and posting times, roughly what it costs a day, when it next runs, how man
No parameter schema on file.
Turn AUTOPILOT POSTING (autoposting, the posting refill) on, change it or pause it. Available on paid plans: on the Free plan switching it on is refused, so relay that and do not retry. Examples: "post 2 times a day on Instagram and Bluesky about …, let me review first", "make it 1 a day", "switch to auto-publish", "pause autopilot". PASS ONLY WHAT CHANGES. It always makes NEW posts (a fresh image
| Parameter | Type | Required | Notes |
|---|---|---|---|
| enabled | boolean | no | on/off. true needs a connected channel and a goal (refused otherwise). false PAUSES it: the recurring job is deleted and nothing new is made. Already-queued posts are untouched. |
| mode | string | no | "review" (default): each batch of fresh posts waits as drafts for approval. "auto": fresh posts are scheduled to publish automatically. |
| daysAhead | number | no | how far ahead to keep the queue full, 1–30 (default 7) |
| postsPerDay | number | no | posts a day, up to the number of posting times ("2 times a day" = 2). 0 (default) = one per posting time. To post MORE per day than there are posting times, also pass postingTimes. |
| postingTimes | array | no | the brand's daily posting times, 24-hour "HH:MM" (e.g. ["09:00","13:00","18:00"]); REPLACES the list. Autopilot makes one post per posting time, and scheduled posts use the same times. get_post_refill |
| timezone | string | no | IANA timezone the posting times are in, e.g. "America/New_York" (default: the brand's, else UTC) |
| channelPostsPerDay | object | no | per channel, how many posts a day (the first N of the posting times), e.g. {"x":1,"instagram":3}. 0 turns a channel off; null resets it to every posting time. Merged into what is saved. |
| goal | string | no | THE BRIEF: what the posts are for — grow followers, drive sales, promote a launch, or other (say what in goalNote). Required to switch it on. |
| goalNote | string | no | THE BRIEF: a line about the goal (e.g. the launch date and what is launching). |
| pillars | array | no | THE BRIEF: content pillars / topics, up to 8 short lines. Every post belongs to one. get_post_refill suggests some from the brand profile. |
| formats | string | no | THE BRIEF: what to make — mix (images with a short video now and then, the default), video (short videos only), carousel (3 new images per post), image. |
| avoid | array | no | THE BRIEF: never mention or show these — topics, claims, competitors, faces. |
| learnings | array | no | notes learned from reviews; REPLACES the list |
| forgetCaptionEdits | boolean | no | true = forget the caption edits learned from past reviews |
| channels | no | REPLACES the list; a channel connected later stays out until added. "all" = every channel, later ones too, only if the user asks | |
| addChannels | array | no | add these |
| removeChannels | array | no | stop posting here |
| excludeChannels | array | no | never these channels, even ones connected later; [] clears |
| boardId | string | no | PINTEREST — which board Pins go on (list_pinterest_boards). Without one, Pinterest is skipped: a Pin on the wrong board is a public mistake, so it is never guessed. |
| chatId | string | no | TELEGRAM — which chat, group or channel posts go to (@username or numeric id). Without one, telegram is skipped: there is no default chat and posting to the wrong one is a public mistake. |
Run AUTOPILOT POSTING (autoposting, paid plans only) NOW instead of waiting for its daily turn, or review the drafts it made: schedule them, delete one, change a caption, or save a note such as "too salesy". By default it is a preview: it returns the posts it WOULD make — each new picture’s scene, the caption, the channels and the price — rendering nothing and queueing nothing. The preview still W
| Parameter | Type | Required | Notes |
|---|---|---|---|
| dryRun | boolean | no | default TRUE (a preview: nothing rendered, nothing queued; the copy it writes bills its model cost). false renders fresh creative and schedules or drafts it per the mode. |
| force | boolean | no | plan a preview even while autoposting is switched off — useful for showing someone what it would do before they turn it on. |
| approve | array | no | REVIEW: draft ids to schedule, or ["all"]. A draft whose time has passed moves to the next free posting slot. |
| discard | array | no | REVIEW: draft ids to delete, or ["all"]. Nothing is posted for them. |
| edit | array | no | REVIEW: change drafts before approving them. Edited copy is screened by the voice rules. |
| learn | array | no | notes for future batches, e.g. "too salesy". Free. |
| redo | object | no | remake one draft from feedback; paid, so without confirm:true it returns only the price |
Use this when the user wants to post to their personal LinkedIn profile. Publishing costs no credits. Publish a post to the user’s connected LinkedIn profile — text, and optionally an image (imageUrl), a VIDEO (videoUrl, with captionsSrt and a videoThumbnailUrl cover), a 2–20 image MULTI-IMAGE post (imageUrls), a LINK post with a real preview card (linkUrl + linkTitle / linkDescription / linkThumb
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no profile, or two, is R |
| hook | string | no | WHAT ANGLE this post is built on, recorded only at publish time. Omit it and this post never votes on which hook works. |
| subject | string | no | WHAT THIS POST IS ABOUT — product, feature, offer or theme (e.g. "winter coat", "free trial"). post_performance's second grouping axis: reuse the exact wording, as with hook. |
| recipe | string | no | the post's FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format |
| ideaId | string | no | short id of the content-plan idea this post came from |
| text | string | yes | the post text |
| imageUrl | string | no | a Hermoso-hosted image URL to attach (≤12MB) — a Hermoso render, or ANY file of the user’s own put through upload_file first. An arbitrary external host is refused (we fetch the bytes ourselves); a UR |
| imageUrls | array | no | a MULTI-IMAGE post: 2–20 Hermoso-hosted images in order, one post the reader swipes through (LinkedIn’s MultiImage API takes a member author on w_member_social). Images only. |
| videoUrl | string | no | a Hermoso-hosted video (a render, or the user’s footage via upload_file). LinkedIn processes it before publishing, which takes a minute; a URL, not a local file path; use upload_file for local files |
| captionsSrt | string | no | SRT caption CONTENT for a videoUrl post (English only, attached during upload) |
| videoThumbnailUrl | string | no | cover image for a videoUrl post (Hermoso-hosted) |
| title | string | no | video title |
| altText | no | image alt text — a string for every image, or an array per slide | |
| linkUrl | string | no | a LINK post with a real preview card. Not with media. |
| linkTitle | string | no | the card headline — LinkedIn never scrapes the page, so without it the card is unlabelled |
| linkDescription | string | no | link card description; LinkedIn does not read the page, so set it here |
| linkThumbnailUrl | string | no | Hermoso-hosted image for the card |
| documentUrl | string | no | a DOCUMENT post — a Hermoso-hosted PDF / PPTX / DOCX (upload_file first) that readers swipe through page by page in the feed; LinkedIn takes up to 100MB and 300 pages. One kind of media per post: not |
| documentTitle | string | no | the title shown on the document (defaults to the file name) |
| mentions | array | no | TAG COMPANIES: write "@Name" in the text where each tag goes and list each company here — its vanity name (the part after linkedin.com/company/), its company page URL, or {organizationId, name}. Hermo |
Use this when the user wants to post to X (Twitter) now. Publish to the user’s connected X (Twitter) account — a single post, a post with an image or video render attached, a reply to an existing post, or a whole THREAD (pass thread as an array and each part is posted as a reply to the one before). This PUBLISHES immediately and PUBLICLY — ALWAYS show the user the exact text and get an explicit
| Parameter | Type | Required | Notes |
|---|---|---|---|
| account | string | no | WHICH connected account of this channel to post as — its @handle or id from list_connector_accounts. Needed only when the profile has more than one x account connected (several and none named is refus |
| brand | string | no | WHICH PROFILE this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no profile, or two, is R |
| hook | string | no | WHAT ANGLE this post is built on, recorded only at publish time. Omit it and this post never votes on which hook works. |
| subject | string | no | WHAT THIS POST IS ABOUT — product, feature, offer or theme (e.g. "winter coat", "free trial"). post_performance's second grouping axis: reuse the exact wording, as with hook. |
| recipe | string | no | the post's FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format |
| ideaId | string | no | short id of the content-plan idea this post came from |
| text | string | no | the post text. 280 characters without X Premium, up to 25,000 with it — write the full thing, it is never truncated. With thread also set, this is the FIRST post and the thread entries follow it (a |
| thread | array | no | a thread: each string is one post, published in order, each replying to the previous (if text is also set it is posted first and these follow). Max 25 including text. Each part follows the same le |
| dryRun | boolean | no | true = post NOTHING and return what this exact post would cost (costCredits). Free. |
| mediaUrl | string | no | a Hermoso render (image or video) to attach to the first post — pass its served URL, or an upload_file url for external media; a URL, not a local file path; use upload_file for local files |
| videoUrl | string | no | alias of mediaUrl for a VIDEO — same as passing it as mediaUrl |
| imageUrl | string | no | alias of mediaUrl for an IMAGE — same as passing it as mediaUrl |
| mediaUrls | array | no | UP TO FOUR Hermoso-hosted media attached to ONE post — X’s own schema caps media_ids at 4. X renders them as a GRID: every image visible at once, nothing to swipe to. That is NOT a carousel, and a num |
| altText | no | accessibility description of the attached media, max 1000 characters — write one whenever you attach a render. Costs a small extra amount: X bills one metadata write PER media. With SEVERAL media, pas | |
| poll | object | no | run a poll on the post. X does not allow a poll and media on the same post, and a poll cannot ride on a thread. |
| replySettings | string | no | restrict who can reply — omit for everyone, which is the right default for a brand post |
| replyToId | string | no | numeric id of an existing X post to reply to. X ONLY ACCEPTS AN API REPLY TO A POST THAT MENTIONS THIS ACCOUNT OR THAT THIS ACCOUNT WROTE (X policy since Feb 2026, every tier below Enterprise): a cold |
| quotePostId | string | no | numeric id of a post to QUOTE — X renders the quoted post inside yours. This is NOT replyToId: a reply sits under the original in its thread, a quote stands alone on your own timeline with the origina |
| communityId | string | no | publish into an X COMMUNITY instead of the main timeline — the number in the community’s own URL (x.com/i/communities/<id>). The connected account must be a MEMBER of it; X answers a non-member and a |
| paidPartnership | boolean | no | label the post a PAID PARTNERSHIP on X — the same disclosure Hermoso already ships for TikTok. OPT-IN ONLY: set it when the post is sponsored, gifted or otherwise paid for, and never assume it on the |
Publish a long-form ARTICLE to the user’s connected X (Twitter) account — X’s own long-form format, which is a different thing from a long POST. Give it a title and a body written in MARKDOWN (or plain prose) and Hermoso converts it into the DraftJS content_state structure X requires: headings, paragraphs, bulleted and numbered lists, blockquotes, bold / italic / strikethrough, links, horizo
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no profile, or two, is R |
| hook | string | no | WHAT ANGLE this post is built on, recorded only at publish time. Omit it and this post never votes on which hook works. |
| subject | string | no | WHAT THIS POST IS ABOUT — product, feature, offer or theme (e.g. "winter coat", "free trial"). post_performance's second grouping axis: reuse the exact wording, as with hook. |
| recipe | string | no | the post's FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format |
| ideaId | string | no | short id of the content-plan idea this post came from |
| title | string | yes | the Article title — X requires one and refuses a draft without it. This is what shows on the timeline card. |
| body | string | yes | the article body, as markdown or plain prose. Markdown headings, lists, quotes, links, emphasis, ``` code fences and | pipe | tables | are all converted to X’s own Article structure. |
| coverImageUrl | string | no | optional cover picture for the Article — a Hermoso render URL or an upload_file url. Must be a STILL image; X Article covers are not videos. |
| headings | string | no | how headings are rendered. “blocks” (default) uses X’s own heading block types for # and ## headings; ### and deeper become bold lines, because X Articles refuse a third-level heading (the reply says |
| allowLossy | boolean | no | publish even though part of the source cannot be represented on X, rendering those parts as plain text. OFF by default and it should usually stay off — silently publishing a user’s copy with formattin |
| publish | boolean | no | default true. Pass false to save it as a DRAFT in the account’s X Articles composer instead — nothing becomes public, the user can review and publish it from X, and it does not spend one of the five d |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
EDIT the text of one of the connected account’s own posts on X. Three things about X’s edit model change how you must use this and none is guessable: (1) X REPLACES THE WHOLE TEXT — there is no partial patch, so pass the complete new post; (2) an edit MINTS A NEW POST ID, and the old id keeps resolving and keeps showing the OLD text, so always hand the user the NEW url afterwards or they will circ
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| postId | string | yes | the numeric X post id — the last part of the post URL. Any id in the post’s edit chain works; Hermoso resolves the newest one. |
| text | string | yes | the FULL new text of the post. It replaces the old text entirely. Same length rule as a new post: 280 characters, or up to 25,000 on an X Premium account. |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Permanently delete one of the connected account’s posts on X. This CANNOT be undone — confirm the exact post with the user first. Costs credits (X bills per API call). Needs X connected.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| id | string | yes | the numeric X post id — the last part of the post URL |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
THE X ANALYTICS TOOL THAT WORKS — impressions, likes, reposts, replies, quotes and bookmarks for any post, PLUS the advertiser numbers (link clicks, profile clicks, engagements) for YOUR OWN posts published in the last 30 days. X serves those private metrics on this same lookup with the user-context connection you already have; that is X's own design, not a workaround. Use this rather than x_post_
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| id | string | yes | the numeric X post id — the last part of the post URL |
| publishedAt | number | no | epoch ms the post went out, if known — lets the private owned-post metrics be requested only inside X's 30-day window instead of costing a refused call |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Advertiser-grade analytics for the connected account’s OWN posts on X — impressions, engagements, LINK CLICKS, profile visits, video views and video completion quartiles. This is the read that answers “did the creative work”, which x_post_metrics cannot: public metrics show likes and reposts, never clicks or video retention. Takes up to 25 post ids in one call. COSTS CREDITS PER POST READ, so ask
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| ids | array | yes | numeric X post ids (max 25) — the last part of each post URL |
| granularity | string | no | default Total |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
The same advertiser-grade X analytics as x_post_insights — impressions, engagements, LINK CLICKS, profile visits, video views and video completion quartiles — over ANY date range instead of only the last 28 hours. This is the one to use for “how did last week’s post do”, “compare these three posts over the month”, or any retrospective: x_post_insights physically cannot see past yesterday, so askin
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| ids | array | yes | numeric X post ids (max 25) — the last part of each post URL |
| startDate | string | no | YYYY-MM-DD or a UTC timestamp; defaults to 28 days before the end |
| endDate | string | no | YYYY-MM-DD or a UTC timestamp; defaults to now |
| granularity | string | no | default Total |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Read the posts mentioning the connected X account — who is talking to the brand, in their own words, newest first. Use it to find what deserves a reply (reply with post_to_x + replyToId) and to mine real objections and customer language for ad copy. COSTS CREDITS PER MENTION RETURNED and per distinct author X returns with them, plus one account lookup — keep maxResults small (default 10) and tell
| Parameter | Type | Required | Notes |
|---|---|---|---|
| maxResults | number | no | how many mentions to pull, 5-100 (default 10) — every one is billed |
| sinceId | string | no | only return mentions newer than this post id |
| paginationToken | string | no | next_token from a previous call, to page further back |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Read the direct messages sent to the connected X account and group them into conversations, newest first — who is waiting on a reply, in their own words. Use it for 'any new DMs on X', 'what did they say', 'anything I need to answer'. Reply with send_x_dm. Messages sent in X Chat are end-to-end encrypted and are NOT in this read, so never say someone did not write from it alone: list_x_chats shows
| Parameter | Type | Required | Notes |
|---|---|---|---|
| maxResults | number | no | how many messages to pull, 1-100 (default 10) — EVERY ONE IS BILLED, and omitting it would make X return 100 |
| conversationId | string | no | read just this conversation (from a previous call). One-to-one ids look like "12-345"; group ids are one long number. |
| participantId | string | no | read just the conversation with this NUMERIC X account id (not an @handle) |
| paginationToken | string | no | nextToken from a previous call, to page further back. There is no since/until filter at X — this is the only way to walk history. |
| eventTypes | array | no | MessageCreate (default), ParticipantsJoin, ParticipantsLeave. The join/leave events carry no message and are billed like any other, so the default is messages only. |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Send ONE direct message from the connected X account, either into an existing conversation (conversationId) or to a named account (participantId — the NUMERIC id, not an @handle; sending to someone with no existing thread starts one). THIS IS A PRIVATE MESSAGE TO A REAL PERSON AND IT CANNOT BE TAKEN BACK: show the user the exact wording and the exact recipient and get an explicit yes BEFORE callin
| Parameter | Type | Required | Notes |
|---|---|---|---|
| text | string | yes | the message, exactly as it should be sent |
| conversationId | string | no | reply into an existing conversation (from list_x_dms) |
| participantId | string | no | the NUMERIC X account id to message — starts a conversation if there is none. list_x_dms returns it beside every message. |
| idempotencyKey | string | no | pass the same key to make a retry safe — a timeout that already sent would otherwise send twice |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
List the boards on the user’s connected Pinterest account — id, name, privacy and pin count. ALWAYS call this before post_to_pinterest and let the USER pick: Pinterest requires a board and Hermoso never chooses one for them. Read-only, 0 credits. Needs Pinterest connected (Settings > Connectors > Pinterest).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| privacy | string | no | filter by board privacy; default is everything the connection can see |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
ORGANIC Pinterest performance — impressions, saves, Pin clicks, outbound clicks and their rates, for the whole ACCOUNT, for the TOP PINS, for the TOP VIDEO PINS (with view-through and average watch time), or for ONE Pin. This is unpaid reach; paid Pinterest ads have their own report. Use scope:"top_pins" to answer "what is actually working on our Pinterest" — it ranks the account’s own Pins by whi
| Parameter | Type | Required | Notes |
|---|---|---|---|
| scope | string | no | default account |
| pinId | string | no | required for scope:"pin" — the id post_to_pinterest returned |
| video | boolean | no | scope:"pin" only — true when the Pin is a VIDEO, which has its own metric set |
| metricTypes | array | no | which metrics; omit for all of the ones valid at this scope. Unknown values are refused with the valid list. |
| sortBy | string | no | top_pins / top_video_pins: the metric to rank by (default the first metric) |
| since | string | no | YYYY-MM-DD, default 30 days ago; Pinterest allows at most 90 days back |
| until | string | no | YYYY-MM-DD, default today |
| limit | number | no | top_pins / top_video_pins: how many (1–50, default 10) |
| appTypes | string | no | app type to report on: ALL, MOBILE, TABLET or WEB |
| splitField | string | no | account: NO_SPLIT | APP_TYPE | OWNED_CONTENT | SOURCE | PIN_FORMAT |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
What people are SEARCHING FOR on Pinterest right now — Pinterest Trends, from the connected Pinterest account (no ad account needed). kind:"keywords" (default): the top 50 trending searches in a market by trendType growing (rising fast this quarter), monthly, yearly or seasonal, filterable by interests, ages, genders and includeKeywords, each with week / month / year growth in search volume. kind:
| Parameter | Type | Required | Notes |
|---|---|---|---|
| kind | string | no | what to list: keywords (default), topics, product_categories or product_category_details |
| region | string | no | keywords: US, CA, DE, FR, ES, IT, DE+AT+CH, GB+IE, IT+ES+PT+GR+MT, PL+RO+HU+SK+CZ, SE+DK+FI+NO, NL+BE+LU, AR, BR, CO, MX, MX+AR+CO+CL, AU+NZ. topics / product categories: US, GB+IE, CA. Default US; "U |
| trendType | string | no | keywords: default growing |
| interests | array | no | keywords: animals, architecture, art, beauty, childrens_fashion, design, diy_and_crafts, education, electronics, entertainment, event_planning, finance, food_and_drinks, gardening, health, home_decor, |
| interest | string | no | topics: ALL, ANIMALS, ARCHITECTURE, ART, BEAUTY, DIY_AND_CRAFTS, EDUCATION, EVENT_PLANNING, FASHION, FOOD_AND_DRINKS, GARDENING, HEALTH, HOME_DECOR, PARENTING, TRAVEL, WEDDING |
| ages | array | no | age bands to filter keyword trends by, e.g. 25-34 |
| genders | array | no | keywords: male, female, unknown. product categories: MALE, FEMALE, UNSPECIFIED |
| includeKeywords | array | no | keywords: only trends containing one of these (max 50) |
| limit | number | no | keywords: 1–50 |
| includeDemographics | boolean | no | true = include demographics in the result |
| verticals | array | no | shopping verticals for product categories: FASHION, HOME_DECOR or BEAUTY |
| engagementType | string | no | engagement measure to report: ENGAGEMENT, OUTBOUND_CLICK or SAVE |
| productCategories | array | no | product_category_details: category names from product_categories, e.g. AREA_RUGS |
| lookbackWindow | no | trend lookback in days: 90, 180, 365 or 730 | |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Read the Pinterest product CATALOG side of the connected account — what a Shopping (CATALOG_SALES) campaign serves from. what: catalogs (default), feeds, feed (feedId), processing_results (feedId — did Pinterest ingest the feed, product counts, errors), item_issues (processingResultId — per-product problems), product_groups (optionally feedId / catalogId), product_counts or products (productGroupI
| Parameter | Type | Required | Notes |
|---|---|---|---|
| what | string | no | which view to read; default catalogs |
| catalogId | string | no | catalog id from what:catalogs, used with feeds and product groups |
| feedId | string | no | feed id from what:feeds, used with feed, processing_results and product_groups |
| processingResultId | string | no | processing result id from what:processing_results, used with item_issues |
| productGroupId | string | no | product group id from what:product_groups, used with products and product_counts |
| batchId | string | no | batch id returned by update_pinterest_catalog_items, used with items_batch |
| adAccountId | string | no | optional — act as this ad account’s owner via Pinterest Business Access (must be shared with this profile) |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Create a Pinterest product CATALOG (RETAIL for products, or HOTEL) — the container feeds and items fill. Read back from Pinterest. Most merchants can skip it: a feed created without catalogId lands in a default retail catalog. 0 credits.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| name | string | yes | catalog name |
| catalogType | string | no | RETAIL (products) or HOTEL |
| adAccountId | string | no | optional — act as this ad account’s owner via Pinterest Business Access (must be shared with this profile) |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Create, edit or delete a Pinterest PRODUCT FEED — the CSV / TSV / XML file of your products Pinterest downloads on a schedule to fill the catalog Shopping ads serve from. action:"create" needs name, location (the feed URL), defaultCountry and defaultLocale (Pinterest refuses a feed without them), optionally format, defaultCurrency, defaultAvailability, catalogId, schedule {time HH:MM, timezone}, c
| Parameter | Type | Required | Notes |
|---|---|---|---|
| action | string | no | default create |
| feedId | string | no | feed to update or delete, from list_pinterest_catalogs what:feeds |
| name | string | no | feed name; required on create |
| location | string | no | the URL Pinterest downloads the feed from |
| format | string | no | feed file format: CSV, TSV, XML or INTEGRATION |
| defaultCountry | string | no | two-letter, e.g. US — required on create |
| defaultLocale | string | no | e.g. en-US — required on create |
| defaultCurrency | string | no | currency for items that omit one, e.g. USD |
| defaultAvailability | string | no | availability for items that omit it: IN_STOCK, OUT_OF_STOCK or PREORDER |
| catalogId | string | no | catalog to attach the feed to, from list_pinterest_catalogs |
| status | string | no | ACTIVE or INACTIVE |
| schedule | object | no | when Pinterest downloads the feed: {time HH:MM, timezone} |
| credentials | object | no | only if the feed URL needs a login |
| confirm | boolean | no | required true for delete |
| adAccountId | string | no | optional — act as this ad account’s owner via Pinterest Business Access (must be shared with this profile) |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Add, change or remove PRODUCTS in a Pinterest catalog directly — no feed file needed. items: up to 1,000 of {itemId (your SKU), operation UPSERT (default) / CREATE / UPDATE / DELETE, attributes {title, description, link, image_link, price e.g. "19.99 USD", availability e.g. "in stock", brand, sale_price, …}}. A create or upsert must carry title, link, image_link and price (refused here otherwise).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| items | array | yes | products to add, change or remove; up to 1,000 per call |
| country | string | no | default US |
| language | string | no | default en-US |
| catalogId | string | no | catalog to write to, from list_pinterest_catalogs |
| adAccountId | string | no | optional — act as this ad account’s owner via Pinterest Business Access (must be shared with this profile) |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Create, edit or delete a Pinterest PRODUCT GROUP — the slice of a catalog a Shopping ad group promotes (one brand, a price band, a custom label). action:"create" needs name, feedId and filters; "update" changes name / description / filters on productGroupId; "delete" needs confirm:true. Read back from Pinterest. 0 credits.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| action | string | no | default create |
| productGroupId | string | no | product group to update or delete |
| name | string | no | product group name; required on create |
| description | string | no | product group description |
| feedId | string | no | feed the group draws products from; required on create |
| filters | object | no | Pinterest product-group filters: {"all_of":[{"BRAND":{"values":["Acme"]}},{"MIN_PRICE":{"values":10}}]} or {"any_of":[…]}. Keys: AVAILABILITY, BRAND, CONDITION, CURRENCY, CUSTOM_LABEL_0-4, CUSTOM_NUMB |
| isFeatured | boolean | no | true = mark the product group as featured |
| confirm | boolean | no | required true for delete |
| adAccountId | string | no | optional — act as this ad account’s owner via Pinterest Business Access (must be shared with this profile) |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
List the SECTIONS of a Pinterest board with their ids (the boardSectionId post_to_pinterest and save_pinterest_pin take), or with sectionId the Pins in one section. Read-only, 0 credits.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| boardId | string | yes | from list_pinterest_boards |
| sectionId | string | no | read the Pins in this section |
| bookmark | string | no | the next page, from the last reply |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Create, rename or delete a section of a Pinterest board. action:"create" needs name; "rename" needs sectionId and name; "delete" needs sectionId and confirm:true (without confirm it reports what would go). Read back from Pinterest. 0 credits.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| boardId | string | yes | from list_pinterest_boards |
| action | string | no | default create |
| sectionId | string | no | rename, delete: from list_pinterest_board_sections |
| name | string | no | create, rename: up to 180 characters |
| confirm | boolean | no | delete: required true |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Read lists on the connected Pinterest account. what: followers, following, following_boards, websites (each site and whether it is claimed; a site that is not claimed explains a refused link) or website_verification (the code to put on a site to claim it). Claiming itself happens in Pinterest. Read-only, 0 credits.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| what | string | yes | which list |
| limit | number | no | up to 250 per page |
| bookmark | string | no | the next page, from the last reply |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Create a board on the connected Pinterest account. Needed because a Pin cannot exist without a board, and a NEW Pinterest business account has none — if list_pinterest_boards comes back empty, make one here rather than telling the user you can't pin. Boards are PUBLIC unless you pass privacy 'SECRET'; a Pin on a secret board is invisible to everyone, so only choose that if the user asked for it.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| name | string | yes | board name, e.g. "Product launches" — keep it something a real Pinterest audience would browse |
| description | string | no | optional board description (≤500 chars) |
| privacy | string | no | default PUBLIC. SECRET hides the board and every Pin on it from everyone but the account owner. |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Use this when the user wants to publish a Pin to Pinterest. Publishing costs no credits. Create a Pin on one of the user’s Pinterest boards from any finished visual they have — image, video, or a 2–5 slide CAROUSEL (pass the slides in order as imageUrls[] and Pinterest publishes one swipeable Pin). Title, description and destination link ride along. The link is what makes a Pin drive traffic, so a
| Parameter | Type | Required | Notes |
|---|---|---|---|
| account | string | no | WHICH connected account of this channel to post as — its @handle or id from list_connector_accounts. Needed only when the profile has more than one pinterest account connected (several and none named |
| brand | string | no | WHICH PROFILE this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no profile, or two, is R |
| hook | string | no | WHAT ANGLE this post is built on, recorded only at publish time. Omit it and this post never votes on which hook works. |
| subject | string | no | WHAT THIS POST IS ABOUT — product, feature, offer or theme (e.g. "winter coat", "free trial"). post_performance's second grouping axis: reuse the exact wording, as with hook. |
| recipe | string | no | the post's FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format |
| ideaId | string | no | short id of the content-plan idea this post came from |
| boardId | string | yes | numeric board id from list_pinterest_boards — the user picks it, never guess |
| imageUrl | string | no | a Hermoso render image URL (or an upload_file url); a URL, not a local file path; use upload_file for local files |
| videoUrl | string | no | a Hermoso render video URL — takes 1–2 minutes to ingest; a URL, not a local file path; use upload_file for local files |
| imageUrls | array | no | CAROUSEL — an ORDERED list of image (and, where the channel allows, video) URLs published as ONE post the viewer swipes through. THIS IS NOT “post several” — it is a single post with several slides, w |
| idempotencyKey | string | no | SAFE RETRIES — any stable string: a repeat of the SAME publish within 24h returns the ORIGINAL post id instead of posting again (an identical publish is also auto-recognised for 10 minutes). On a time |
| allowDuplicate | boolean | no | post it even though an identical post was just made or attempted. Only pass this when the user genuinely wants the same thing posted twice, or when you have LOOKED at the account and confirmed a timed |
| title | string | no | Pin title, max 100 characters |
| description | string | no | Pin description, max 800 characters — this is what Pinterest search reads |
| link | string | no | destination URL the Pin clicks through to |
| altText | no | accessibility alt text, max 500 characters. PIN-LEVEL: Pinterest’s API has no per-item alt text at all, so on a CAROUSEL the FIRST description is used for the whole Pin and the reply states that the o | |
| slideText | array | no | PINTEREST CAROUSEL ONLY — per-slide title, description and destination LINK, one object per slide in slide order. This is the ONLY genuine per-slide caption on any channel Hermoso publishes to: slide |
| coverImageUrl | string | no | video Pins only — a render to use as the cover frame |
| coverAtMs | number | no | THE VIDEO COVER of a video Pin, as ONE frame: milliseconds from the start (7000 = the frame at 7s). That frame is cut and sent as the Pin cover image. coverImageUrl wins. |
| boardSectionId | string | no | optional section within the board |
The Pins on one of the account’s boards — or, with no boardId, the account’s own Pins across all of them. Each row carries the Pin id, title, description, destination link, alt text, board, creation date, and whether it HAS BEEN PROMOTED in an ad. THIS IS WHERE THE pinId EVERY OTHER PIN TOOL NEEDS COMES FROM: post_to_pinterest returns an id only at the instant it pins, so an agent that did not its
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| boardId | string | no | numeric board id from list_pinterest_boards — omit for the account’s own Pins across all boards |
| limit | number | no | 1–100, default 25 |
| cursor | string | no | the cursor a previous call returned |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Edit a published Pin — its title, description, destination link, alt text, or which board it sits on. Only send the fields that should change. TWO LIMITS TO STATE BEFORE OFFERING THIS. (1) Pinterest marks its Update Pin endpoint "currently in beta and not available to all apps" in its own API description, so it may be refused outright whatever the account’s scopes or access tier — reconnecting can
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| pinId | string | yes | numeric Pin id from list_pinterest_pins |
| title | string | no | max 100 characters |
| description | string | no | max 800 characters — the text Pinterest search reads |
| link | string | no | destination URL, max 2048 |
| altText | string | no | accessibility alt text, max 500 |
| boardId | string | no | move the Pin to this board |
| boardSectionId | string | no | section within the board |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Save an existing Pin onto another of the account’s boards. This is the GENERALLY AVAILABLE way to get a Pin onto the right board — unlike update_pinterest_pin, which Pinterest keeps in a limited beta — so reach for it first when a Pin is on the wrong board. It COPIES rather than moves: Pinterest’s save endpoint creates a new Pin and the original stays where it is, so delete that one with delete_pi
| Parameter | Type | Required | Notes |
|---|---|---|---|
| pinId | string | yes | numeric Pin id |
| boardId | string | yes | the board to save it to, from list_pinterest_boards — the user picks, never guess |
| boardSectionId | string | no | optional section id within the destination board |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
PERMANENTLY delete a Pin. Pinterest has no undelete and no archive for one. Call it WITHOUT confirm first: nothing is deleted, and it answers with the Pin’s real title, its lifetime saves and impressions, and whether it HAS BEEN PROMOTED in an ad — all read back from Pinterest. Show the user that, get an unambiguous yes, then call again with confirm:true plus confirmName (its exact title) once it
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| pinId | string | yes | numeric Pin id from list_pinterest_pins |
| confirm | boolean | no | REQUIRED true — deletion is permanent |
| confirmName | string | no | the Pin’s EXACT title as the unconfirmed call reported it — required once it has saves or has been promoted |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Rename a board, rewrite its description, or change its privacy. Warning: SETTING A BOARD TO SECRET HIDES EVERY PIN ON IT from everyone but this account — nothing errors and nothing is deleted, the Pins simply stop being public, which is the Pinterest flavour of a post that looks published and is not. Say so and get a yes before doing it; it IS reversible (set PUBLIC again), and the read-back repor
| Parameter | Type | Required | Notes |
|---|---|---|---|
| boardId | string | yes | numeric board id from list_pinterest_boards |
| name | string | no | new board name |
| description | string | no | max 500 characters |
| privacy | string | no | SECRET hides every Pin on the board from everyone but this account |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
PERMANENTLY delete a board AND EVERY PIN ON IT. This is the heaviest thing that can be done to a Pinterest account and there is no undelete. Call it WITHOUT confirm first: nothing is deleted, and it answers with the board’s real name, how many Pins are on it, how many people FOLLOW it and how many collaborators lose access — all read back from Pinterest. Show the user that, then call again with co
| Parameter | Type | Required | Notes |
|---|---|---|---|
| boardId | string | yes | numeric board id from list_pinterest_boards |
| confirm | boolean | no | REQUIRED true — the board and its Pins are gone for good |
| confirmName | string | no | the board’s EXACT name as the unconfirmed call reported it |
| confirmChildren | number | no | the number of Pins the unconfirmed call reported on the board |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
WHICH AUDIENCE SEGMENT actually delivered on Pinterest — ad performance broken down by keyword, targeted interest, age bucket, gender, location, region, country, placement, app type, media type and more. targetingTypes is REQUIRED because it is what the report breaks down BY. scope:"account" covers the whole ad account; "campaign" / "adGroup" / "ad" each REQUIRE their own id list, because Pinteres
| Parameter | Type | Required | Notes |
|---|---|---|---|
| adAccountId | string | no | Pinterest ad account id; omit when only one is shared with this profile |
| scope | string | no | default account |
| targetingTypes | array | yes | REQUIRED — e.g. KEYWORD, AGE_BUCKET, GENDER, LOCATION, PLACEMENT, MEDIA_TYPE, TARGETED_INTEREST, PINNER_INTEREST, COUNTRY, REGION |
| campaignIds | array | no | campaign ids to report on; needed for scope campaign |
| adGroupIds | array | no | ad group ids to report on; needed for scope adGroup |
| adIds | array | no | ad ids to report on; needed for scope ad |
| since | string | no | start date, YYYY-MM-DD; at most 90 days back |
| until | string | no | end date, YYYY-MM-DD |
| granularity | string | no | time bucket: TOTAL, DAY, HOUR, WEEK or MONTH |
| columns | array | no | Pinterest metric column names to return |
WHO the Pinterest audience IS, rather than what it did — interest categories each carrying an affinity INDEX, plus demographics (ages, countries, devices, genders, metros). Three audiences: YOUR_TOTAL_AUDIENCE, YOUR_ENGAGED_AUDIENCE, and PINTEREST_TOTAL_AUDIENCE as the baseline to compare the other two against. This is an input to a creative brief, not a performance report. SAY THIS WHEN REPORTING
| Parameter | Type | Required | Notes |
|---|---|---|---|
| adAccountId | string | no | Pinterest ad account id; omit when only one is shared with this profile |
| insightType | string | no | default YOUR_TOTAL_AUDIENCE |
List the Google Business Profile listings SHARED WITH THIS PROFILE — id, title, address, website and Maps link. These are the only listings anything here can post to or read: one Google login often manages several businesses (an agency manages its clients’), and the user ticks which of them belong to this profile. Call this before posting whenever more than one is shared and let the USER pick: a P
No parameter schema on file.
Publish a Post to the brand’s Google Business Profile — the panel that appears on Google Search and Maps for the business. Text, optionally ONE PHOTO, and a call-to-action button. Google’s Posts API accepts NO VIDEO, so pass a still image. This PUBLISHES immediately and publicly on the business listing — show the user the exact text, photo and button and get an explicit yes BEFORE calling. If the
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no profile, or two, is R |
| hook | string | no | WHAT ANGLE this post is built on, recorded only at publish time. Omit it and this post never votes on which hook works. |
| subject | string | no | WHAT THIS POST IS ABOUT — product, feature, offer or theme (e.g. "winter coat", "free trial"). post_performance's second grouping axis: reuse the exact wording, as with hook. |
| recipe | string | no | the post's FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format |
| ideaId | string | no | short id of the content-plan idea this post came from |
| summary | string | no | the body text of the Post |
| locationId | string | no | which listing, e.g. 'locations/123' from list_business_locations — only needed when the account manages more than one |
| imageUrl | string | no | a Hermoso render image URL (or an upload_file url) to show on the Post |
| topicType | string | no | default STANDARD |
| actionType | string | no | the button on the Post |
| link | string | no | the URL the button opens — not for CALL, and ignored on an OFFER |
| title | string | no | headline — REQUIRED for EVENT and OFFER |
| startDate | string | no | YYYY-MM-DD — REQUIRED for EVENT and OFFER |
| endDate | string | no | YYYY-MM-DD, defaults to startDate |
| couponCode | string | no | OFFER only |
| redeemOnlineUrl | string | no | OFFER only — this is the link Google actually uses on an offer |
| termsConditions | string | no | OFFER only |
| languageCode | string | no | BCP-47 language of the Post, default 'en' |
List the Posts currently on the brand’s Google Business Profile listing — text, topic type, state (LIVE / PROCESSING / REJECTED / SCHEDULED / RECURRING), button and timestamps. Use it to see what is already showing before writing another, or to get the id of one to remove. Read-only, 0 credits. Needs Google Business Profile connected.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| locationId | string | no | which listing, from list_business_locations — only needed when there is more than one |
| limit | number | no | how many to return, max 100 (default 20) |
Remove a Post from the brand’s Google Business Profile listing. This takes it off Google Search and Maps immediately and CANNOT be undone — confirm with the user first. Pass the full post name from list_google_business_posts. Needs Google Business Profile connected.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| postId | string | yes | the full post name from list_google_business_posts (accounts/…/locations/…/localPosts/…) |
The reviews customers have left on the brand’s Google Business Profile listing — star rating, reviewer, the text, when it landed, and whether the business has replied. For a local business this is the highest-leverage surface there is: an unanswered review sits on the listing next to the ad you paid for. The reply says which ones have NO answer yet, so you can work the list rather than read it. Go
| Parameter | Type | Required | Notes |
|---|---|---|---|
| locationId | string | no | which listing — omit when only one is shared with this profile |
| limit | number | no | 1–50, default 20 |
| orderBy | string | no | default newest first |
| pageToken | string | no | next page token from the previous reply |
Answer a customer review publicly, as the business, on the brand’s Google Business Profile listing — or delete a reply that is already there. THIS IS AN UPSERT: a listing has exactly one reply per review, so replying to a review that already has an answer REPLACES it rather than adding a second. Google only accepts replies on a VERIFIED listing. Deleting is public and immediate, so it is confirm-g
| Parameter | Type | Required | Notes |
|---|---|---|---|
| reviewId | string | yes | from list_google_business_reviews |
| comment | string | no | the public reply text — required unless you are deleting |
| locationId | string | no | which listing — omit when only one is shared |
| delete | boolean | no | true removes the existing reply instead of writing one |
| confirm | boolean | no | required for delete:true |
The questions the public has asked on the brand’s Google Business Profile listing, with the answers so far and how many people upvoted each question. Unanswered questions sit publicly on the listing and are read as "this business does not respond" — the reply names the ones with no answer at all. Read-only, 0 credits. Needs Google Business Profile connected and the project approved.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| locationId | string | no | listing id from list_business_locations; omit when only one is shared |
| limit | number | no | 1–20, default 10 |
| pageToken | string | no | next page token from the previous reply |
Post the business’s answer to a public question on the brand’s Google Business Profile listing, or delete the answer already there. THIS IS AN UPSERT — one answer per account, so answering again REPLACES the previous one rather than adding a second. Deleting is public and immediate and is confirm-gated. Needs Google Business Profile connected and the project approved.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| questionId | string | yes | from list_google_business_questions |
| text | string | no | the answer — required unless deleting |
| locationId | string | no | listing id from list_business_locations; omit when only one is shared |
| delete | boolean | no | true removes the existing answer instead of posting one; needs confirm |
| confirm | boolean | no | required for delete:true |
How the brand’s Google Business Profile listing actually performed — impressions on Google Search and Maps (desktop and mobile), calls, website clicks, direction requests, messages and bookings — over the last N days. For a local business this is the real-world demand signal, and it is the number an ad campaign should be judged against. NOTE: Google discontinued PER-POST insights in February 2023
| Parameter | Type | Required | Notes |
|---|---|---|---|
| locationId | string | no | which listing, from list_business_locations |
| days | number | no | how many days back, default 30 |
| metrics | array | no | optional subset of Google’s daily metrics (BUSINESS_IMPRESSIONS_DESKTOP_MAPS, BUSINESS_IMPRESSIONS_DESKTOP_SEARCH, BUSINESS_IMPRESSIONS_MOBILE_MAPS, BUSINESS_IMPRESSIONS_MOBILE_SEARCH, BUSINESS_DIRECT |
Read everything Google holds on one of the brand’s Google Business Profile listings — business name, address, phone numbers, website, categories, description, regular and special hours, service area, labels, store code, open state, and whether the listing can carry a Post at all. This is the listing AS THE MERCHANT LAST SET IT, which is exactly what update_business_location edits; it can differ fr
| Parameter | Type | Required | Notes |
|---|---|---|---|
| locationId | string | no | which listing, e.g. 'locations/123' from list_business_locations — only needed when more than one is shared with this profile |
Change the brand’s Google Business Profile listing — hours, phone, website, description, categories, service area, labels, store code, address or the business name. THIS EDITS THE PANEL ON GOOGLE SEARCH AND MAPS, immediately and publicly: there is no draft, no preview and no undo. Pass ONLY what changes, in fields, keyed by Google’s own field names: websiteUri, phoneNumbers, regularHours, specia
| Parameter | Type | Required | Notes |
|---|---|---|---|
| fields | object | yes | the changes, keyed by Google’s Location field names, e.g. {"websiteUri":"https://example.com"} or {"regularHours":{"periods":[…]}} |
| locationId | string | no | which listing, from list_business_locations — only needed when more than one is shared with this profile |
| confirm | boolean | no | true ONLY after the user has seen the exact before-and-after and approved it |
| confirmName | string | no | the listing’s CURRENT name, echoed back — required when changing title or storefrontAddress |
| dryRun | boolean | no | validate with Google and write nothing (needs no confirm) |
Read the Google Business Profile ACCOUNT that owns one of the brand’s listings — the account name, its type (a personal Google account, a location group, a user group or an organization), the connected user’s role on it (primary owner / owner / manager / site manager), the account’s verification state and the permission level. Use it to answer “can we actually edit this listing?” and “whose accoun
| Parameter | Type | Required | Notes |
|---|---|---|---|
| locationId | string | no | which listing, from list_business_locations — only needed when more than one is shared with this profile |
Use this when the user wants to upload a video or Short to YouTube. Publishing costs no credits. Publish a finished video to the brand’s connected YouTube channel. Pass a Hermoso render URL (or an upload_file url for a local/external file). PUBLISHES PUBLICLY BY DEFAULT: a plain "post this to YouTube" puts it ON the channel (confirm the title with the user, as for any publish) and notifies subscri
| Parameter | Type | Required | Notes |
|---|---|---|---|
| account | string | no | WHICH connected account of this channel to post as — its @handle or id from list_connector_accounts. Needed only when the profile has more than one youtube account connected (several and none named is |
| brand | string | no | WHICH PROFILE this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no profile, or two, is R |
| hook | string | no | WHAT ANGLE this post is built on, recorded only at publish time. Omit it and this post never votes on which hook works. |
| subject | string | no | WHAT THIS POST IS ABOUT — product, feature, offer or theme (e.g. "winter coat", "free trial"). post_performance's second grouping axis: reuse the exact wording, as with hook. |
| recipe | string | no | the post's FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format |
| ideaId | string | no | short id of the content-plan idea this post came from |
| videoUrl | string | yes | the video to post — a Hermoso render URL or an upload_file url; a URL, not a local file path; use upload_file for local files |
| title | string | no | REQUIRED in practice — the headline every search result and the Shorts feed shows (≤100 chars); an upload with no title is refused |
| description | string | no | REQUIRED in practice — what the video shows, a line about the brand, a link and a few #hashtags (≤5000 chars); YouTube search, suggested videos and the Shorts feed rank on it, so an upload with no des |
| tags | array | no | up to 30 tags |
| thumbnailUrl | string | no | The custom thumbnail: a Hermoso-hosted image. Omit it and a representative frame of the video is set, free; "auto" keeps YouTube’s pick. Custom thumbnails need a verified channel. |
| privacy | string | no | default public (live + searchable on the channel); unlisted = link-only (the ad-ready setting, or to add YouTube music in Studio first — only when the user asks); private = eyes-only (cannot run as an |
| categoryId | string | no | YouTube category id, NUMERIC — default "22" (People & Blogs), which is wrong for most ads. 1 Film & Animation · 2 Autos & Vehicles · 10 Music · 15 Pets & Animals · 17 Sports · 19 Travel & Events · 20 |
| publishAt | string | no | SCHEDULE the publish — ISO 8601, e.g. "2026-09-01T15:00:00Z", and it must be in the future. YouTube only allows this on a PRIVATE video and makes it PUBLIC at that moment, so pass privacy:"private" (o |
| notifySubscribers | boolean | no | THE DEFAULT FOLLOWS PRIVACY. privacy:"public" NOTIFIES the channel's subscribers — that is YouTube's own default and normally what someone publishing publicly wants. privacy:"unlisted" and "private" d |
| aiGenerated | boolean | no | YouTube’s “altered or synthetic content” declaration (containsSyntheticMedia). OMIT IT and Hermoso decides from provenance: a Hermoso render is declared, a video the user uploaded through upload_file |
| platformCover | boolean | no | VIDEO COVER. Omit it (the default) and Hermoso sets the video’s best frame — the same frame as its Library thumbnail — as the cover (YouTube custom thumbnail; the same as thumbnailUrl:"auto" when true |
| coverAtMs | number | no | THE VIDEO COVER (the custom thumbnail), as ONE frame: milliseconds from the start (7000 = the frame at 7s). That frame is cut from the upload and set with thumbnails.set. thumbnailUrl wins; this beats |
| paidProductPlacement | boolean | no | true = contains PAID product placement (sponsored) — YouTube shows the disclosure; legally required for sponsored content |
| brandPartner | string | no | sponsoring brand's channel id (UC…) or @handle — gives that brand access to run the video as a partnership ad; "" unlinks (update only) |
Read the brand’s connected YouTube channel — title + subscriber / view / video counts (for reporting), and whether CUSTOM THUMBNAILS are on (customThumbnails: enabled | needs_verification | unavailable | unknown). When they are not on, the reply carries YouTube’s own step-by-step to turn them on (a phone verification in YouTube Studio), so check this before promising a custom thumbnail. Needs a co
| Parameter | Type | Required | Notes |
|---|---|---|---|
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
List the connected channel’s OWN recent uploads — video id, title, publish date and privacy — so you can resolve a video WITHOUT asking the user for a link. Call this whenever the user names a video loosely ("my latest", "the shorts one", part of a title) and match it yourself; only ask them when two titles are genuinely ambiguous. This is the tool that gets you the videoId every other YouTube too
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| limit | number | no | how many recent uploads to return (default 25, max 50) |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Per-VIDEO performance for a video on the connected channel — views, estimated minutes watched, average view duration, average view PERCENTAGE (the retention number that tells you whether the hook held), likes, comments, shares and subscribers gained. Use it for "how did that video do", "which upload performed best", or to judge an ad before spending more behind it. youtube_channel only returns cha
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| videoId | string | yes | the YouTube video id (the v= part of the watch URL, or the videoId returned by post_to_youtube) |
| startDate | string | no | YYYY-MM-DD, default 28 days ago |
| endDate | string | no | YYYY-MM-DD, default today |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
The YouTube Analytics reports that say WHERE views came from, WHO watched and WHERE they stopped watching — the questions youtube_channel (totals) and youtube_video_insights (one video, flat) cannot answer. Pick a report: day / month (time series) · country / province (US states) / city / dma (geography) · trafficSource (search vs browse vs suggested vs shorts feed vs external — the single most us
| Parameter | Type | Required | Notes |
|---|---|---|---|
| report | string | no | which report (default day); contentType = Shorts vs long-form vs live |
| contentType | string | no | keep only this type (videoOnDemand = long-form) — any report except topVideos, the *Detail reports and audienceRetention |
| splitByContentType | boolean | no | add a content-type column instead of filtering |
| videoIds | array | no | narrow to these videos — audienceRetention requires exactly ONE, because the curve is per video |
| parent | string | no | required by the *Detail reports: the ONE parent to drill into, e.g. "YT_SEARCH" / "SUBSCRIBER" / "RELATED_VIDEO" for trafficSourceDetail, "EMBEDDED" for playbackLocationDetail |
| startDate | string | no | YYYY-MM-DD, default 28 days ago |
| endDate | string | no | YYYY-MM-DD, default today |
| limit | number | no | rows, within YouTube’s own cap for that report |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
THE ONLY PLACE YOUTUBE PUBLISHES THUMBNAIL IMPRESSIONS AND THUMBNAIL CTR. This is a different API from youtube_channel_report — YouTube’s bulk Reporting API — and for a product that generates thumbnails it is the number that says whether the thumbnail actually worked. Reports: thumbnails (impressions + CTR per video per day) · thumbnails_by_source (the same, split by traffic source, traffic source
| Parameter | Type | Required | Notes |
|---|---|---|---|
| report | string | no | default thumbnails |
| days | number | no | how many recent daily files to read (1–14, default 7) |
| since | string | no | YYYY-MM-DD — only files whose data starts on or after this |
| until | string | no | YYYY-MM-DD — only files whose data starts before this |
| schedule | boolean | no | false = do not create the job if it is missing; just report that none exists |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
The YouTube BULK reporting jobs running on this channel — which report each one generates, its report type id, and when it was scheduled. Call this to find out whether thumbnail-CTR history is already accumulating, and since when, BEFORE promising a user a number: the bulk API can only answer about days after a job existed. Read-only, 0 credits.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Stop a YouTube bulk reporting job. IRREVERSIBLE IN A WAY THAT IS EASY TO MISS: the job IS the history — deleting it discards every daily CSV it has accumulated, and a replacement job starts over with only a 30-day backfill, so anything older than that is gone for good. Call WITHOUT confirm first: nothing is deleted and you get the real job read back from YouTube (its report type and when it was sc
| Parameter | Type | Required | Notes |
|---|---|---|---|
| jobId | string | yes | from list_youtube_report_jobs |
| confirm | boolean | no | true only after the user has seen the job and said yes |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Edit an existing video on the connected channel: title, description, tags, privacy (unlisted | public | private), category, schedule (publishAt; "" clears it), madeForKids, aiGenerated, licence, embedding, stats visibility, languages + per-language translations, paid-placement disclosure and brand partner. THIS IS HOW YOU FLIP AN UNLISTED UPLOAD PUBLIC. Making a video PUBLIC puts it on the channel
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| videoId | string | yes | the YouTube video id |
| title | string | no | ≤100 chars |
| description | string | no | ≤5000 chars |
| tags | array | no | video tags, up to 30; omit to leave the current tags |
| privacy | string | no | public = live on the channel; confirm with the user first |
| categoryId | string | no | numeric YouTube category id |
| publishAt | string | no | ISO date-time to go public; keeps it private until then; "" clears a schedule |
| madeForKids | boolean | no | true = made for kids; false = not made for kids |
| aiGenerated | boolean | no | YouTube’s “altered or synthetic content” label. OMIT IT and the value already on the post stays; a post that never had one is decided from provenance (a Hermoso render is declared, real footage from u |
| paidProductPlacement | boolean | no | true = contains PAID product placement (sponsored) — YouTube shows the disclosure; legally required for sponsored content |
| brandPartner | string | no | sponsoring brand's channel id (UC…) or @handle — gives that brand access to run the video as a partnership ad; "" unlinks (update only) |
| defaultLanguage | string | no | language of title/description, e.g. "en" |
| defaultAudioLanguage | string | no | spoken language, e.g. "es" |
| localizations | object | no | translated title/description per language, e.g. {"es":{"title":"…"}}; needs defaultLanguage |
| license | string | no | YouTube licence: youtube (standard) or creativeCommon |
| embeddable | boolean | no | true = allow embedding on other sites |
| publicStatsViewable | boolean | no | true = show public view and like counts |
| recordingDate | string | no | YYYY-MM-DD |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
PERMANENTLY delete a video from the connected YouTube channel. IRREVERSIBLE — YouTube has no trash and no undelete, and the video's views, comments and every link or embed pointing at it go with it. Call it WITHOUT confirm first: nothing is deleted, and it reports the video's REAL title, privacy, view count and comment count read back from YouTube. Show the user that, get an unambiguous yes, then
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| videoId | string | yes | the YouTube video id (from list_youtube_videos) |
| confirm | boolean | no | REQUIRED true — deletion is permanent and cannot be undone |
| confirmTitle | string | no | the video's EXACT title as the unconfirmed call reported it — required once the video is public or has any views or comments |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
APPLY THE BRAND TO THE CHANNEL ITSELF — banner art, description, keywords, country and the trailer non-subscribers see. Every other YouTube tool brands the videos; this brands the page they sit on. Under the hood channels.update is a PUT, so the CURRENT settings are read and merged first — otherwise setting a description would silently wipe the channel's keywords, country and trailer. AND YOUTUBE
| Parameter | Type | Required | Notes |
|---|---|---|---|
| description | string | no | the channel description — the About text |
| keywords | string | no | channel keywords, COMMA-SEPARATED (Google's wire format is one string, not a list) |
| country | string | no | two-letter country code for the channel |
| defaultLanguage | string | no | the channel's default language |
| unsubscribedTrailer | string | no | video id of the trailer shown to people who are not subscribed |
| title | string | no | the channel title — YouTube often accepts and ignores this; the result says whether it stuck |
| bannerImageUrl | string | no | any public https image URL to upload as the channel banner — a Hermoso render, or ANY file of your own brought in with upload_file |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Set (or remove) the BRANDING WATERMARK — the small subscribe badge overlaid on EVERY video on the channel, including ones uploaded later. One generated asset brands the whole channel at once, which is why it is worth doing before a batch of uploads rather than after. YouTube wants a SQUARE image, at least 150x150, under 10MB, and it renders SMALL: a full logo lockup with text will not read at that
| Parameter | Type | Required | Notes |
|---|---|---|---|
| action | string | no | defaults to 'set' when an imageUrl is given |
| imageUrl | string | no | any public https image URL — a Hermoso render, or ANY file of your own brought in with upload_file. Square, at least 150x150 |
| timingType | string | no | leave off for a watermark that shows for the whole video |
| offsetMs | number | no | when the watermark appears, relative to timingType |
| durationMs | number | no | how long it stays on screen |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Views, likes and comment counts for up to 50 YouTube videos IN ONE CALL, which is how to answer "how are my last twenty uploads doing" without one youtube_video_insights per video. Pass videoIds from list_youtube_videos. IT CARRIES NO TITLES, and that is the resource rather than a bug: VideoStatsSnippet publishes only publishTime, so join on videoId with list_youtube_videos when a name is needed.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| videoIds | array | yes | up to 50 video ids, from list_youtube_videos |
| part | array | no | defaults to snippet + statistics. An unknown part 400s the whole call, so it is refused here |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Set the CUSTOM THUMBNAIL on a video already on the connected channel, using a Hermoso image. The thumbnail is the single biggest lever on YouTube click-through and YouTube otherwise auto-picks a frame, so a published video without one is leaving reach on the table. It changes ONLY the thumbnail — video, title and privacy are untouched — but it is public and immediate, so show the user which image
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| videoId | string | yes | the YouTube video id (what post_to_youtube returned) |
| imageUrl | string | yes | A Hermoso-hosted image URL. An arbitrary external host is refused. |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Read the comments under a video on the connected channel — the questions, objections and exact wording real viewers use. Same raw material for ad copy that list_meta_comments gives you on Meta. Returns author, text, like count, timestamp, reply count and comment id, newest first — plus imageUrl when a viewer attached an image or GIF to the comment (a signed YouTube link that expires six hours af
| Parameter | Type | Required | Notes |
|---|---|---|---|
| videoId | string | yes | the YouTube video id |
| limit | number | no | max comments, default 25, cap 100 |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Every reply under ONE comment on the connected channel, newest pages first via cursor: check that a reply the brand posted landed, or whether someone already answered a viewer before replying. Pass the top-level comment id from list_youtube_comments (each comment line shows its id and reply count); a REPLY id is recognised and its whole thread is returned, with note and requestedId saying so.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| commentId | string | yes | the top-level comment id from list_youtube_comments |
| limit | number | no | max replies per page, default 25, cap 100 |
| cursor | string | no | nextCursor from the previous page |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Post a public reply to a comment on the connected channel, as the channel. This is PUBLIC and immediate — show the user the exact reply text and get an explicit yes before calling. Get commentId from list_youtube_comments. Needs a connected YouTube channel.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| commentId | string | yes | id of the comment to reply to (from list_youtube_comments) |
| text | string | yes | the reply, shown publicly under the video |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
List the playlists on the connected YouTube channel — id, title, description, privacy and video count. Pass a playlistId to get that ONE playlist plus its entries in order. IMPORTANT: each entry carries BOTH a videoId and an itemId; itemId is the playlist-ENTRY id and is what manage_youtube_playlist_items needs to remove or re-order a row, because one video can appear in a playlist more than
| Parameter | Type | Required | Notes |
|---|---|---|---|
| playlistId | string | no | one playlist to open, with its entries. Omit to list the channel’s playlists. |
| limit | number | no | max rows, default 25, cap 50 (YouTube’s own maximum) |
| pageToken | string | no | nextPageToken from a previous call |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Create, update or delete a playlist on the connected channel. CREATE makes it PRIVATE unless you pass privacy — a public playlist appears on the channel immediately, so publishing is always a deliberate choice. UPDATE merges: an omitted title or description is LEFT ALONE rather than erased. DELETE is confirm-gated and does NOT delete the videos — what dies is the playlist, its ordering and every l
| Parameter | Type | Required | Notes |
|---|---|---|---|
| action | string | yes | create | update | delete |
| playlistId | string | no | required for update and delete |
| title | string | no | required on create; on update, omit to leave unchanged |
| description | string | no | on update, omit to leave unchanged |
| privacy | string | no | default private on create |
| confirm | boolean | no | required to delete |
| confirmTitle | string | no | the playlist’s EXACT title — required to delete one that is public or non-empty |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Add a video to a playlist, remove an entry, or move an entry to a new position. ADD takes playlistId + videoId and returns the new entry’s itemId. REMOVE and MOVE take that itemId — NOT the videoId — because one video can sit in a playlist more than once, so a videoId does not identify which row to touch; list_youtube_playlists with a playlistId returns the itemId of every entry. Removing an e
| Parameter | Type | Required | Notes |
|---|---|---|---|
| action | string | yes | add | remove | move |
| playlistId | string | no | required for add |
| videoId | string | no | required for add |
| itemId | string | no | the playlist-ENTRY id — required for remove and move (from list_youtube_playlists) |
| position | number | no | zero-based position; required for move, optional on add (default: appended) |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
THE SHELVES ON THE CHANNEL HOMEPAGE — what a visitor sees first, and the only place a chosen playlist can be put above YouTube’s own default layout. action:'list' reads them in the order they appear, 'create' adds one, 'update' replaces one, 'delete' removes one (confirm-gated, and there is no undo — the layout has to be rebuilt by hand). EVERY WRITE IS PUBLIC IMMEDIATELY: a channel homepage is no
| Parameter | Type | Required | Notes |
|---|---|---|---|
| action | string | no | defaults to 'list' |
| type | string | no | singlePlaylist | multiplePlaylists | popularUploads | recentUploads | likes | allPlaylists | likedPlaylists | recentPosts | recentActivity | liveEvents | upcomingEvents | completedEvents | multipleCha |
| title | string | no | the heading, and only multiplePlaylists and multipleChannels take one — YouTube writes the heading for every other type |
| playlists | array | no | playlist ids, from list_youtube_playlists. Required for singlePlaylist (exactly one) and multiplePlaylists |
| channels | array | no | channel ids to feature. Required for multipleChannels |
| position | number | no | zero-based position on the homepage. Leave it off and YouTube places the section |
| style | string | no | leave it off to let YouTube choose |
| sectionId | string | no | for update and delete — from action:'list' |
| hl | string | no | language for the returned titles, e.g. "en" |
| confirm | boolean | no | must be true to actually delete |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
PUT A CUSTOM COVER ON A PLAYLIST. Without one YouTube shows the first video's thumbnail. Action:'list' reads what is on a playlist, 'set' uploads a cover, 'delete' removes it. ImageUrl must be a Hermoso-hosted URL, up to 50MB. Nothing else is fetched server-side. YOUTUBE ANSWERS EVERY FAILURE HERE AS AN HTTP 500 "Internal error encountered" with the real reason buried inside it, so a plain relay w
| Parameter | Type | Required | Notes |
|---|---|---|---|
| playlistId | string | yes | from list_youtube_playlists |
| action | string | no | defaults to 'list' |
| imageUrl | string | no | For action:'set' |
| imageId | string | no | for action:'delete' — from action:'list' |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
List the caption/subtitle tracks on one of the connected channel’s videos, and optionally DOWNLOAD one as text. A caption TRACK is not the same thing as burned-in captions: a track is what YouTube indexes the video by, what a viewer toggles on, and what accessibility depends on. Each row says whether YouTube generated it automatically (isAutoGenerated, trackKind ASR) — those are read-only and ca
| Parameter | Type | Required | Notes |
|---|---|---|---|
| videoId | string | yes | the YouTube video id — captions are listed per video |
| download | string | no | a captionId or a language code to also download as text |
| format | string | no | download format, default srt |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Attach a real subtitle track to one of the connected channel’s videos, replace one, or delete one. Pass the caption file BODY itself as text (SRT or WebVTT), not a URL. language is required and may not be guessed — YouTube uses it to decide which viewers see the track. A track YouTube generated itself (trackKind ASR) is refused by name: YouTube owns it and it cannot be edited or deleted throug
| Parameter | Type | Required | Notes |
|---|---|---|---|
| action | string | yes | upload | update | delete |
| videoId | string | no | required for upload; strongly recommended for update and delete so the track can be resolved and read back |
| captionId | string | no | required for update and delete (from list_youtube_captions) |
| text | string | no | the caption file body — SRT or WebVTT. Required for upload. |
| language | string | no | BCP-47 code such as en, es, pt-BR. Required for upload. |
| name | string | no | a label shown in YouTube’s caption picker |
| draft | boolean | no | true keeps the track unpublished while you review it |
| confirm | boolean | no | required to delete |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Moderate a comment on the connected channel — the YouTube twin of moderate_meta_comment. ACTIONS: publish (make it visible again) · hold (hide it pending review) · reject (hide it as unfit for display) · spam (report it to YouTube — a REPORT, not a removal, so do not tell the user it is gone) · delete (permanent; YouTube has no undelete for a comment). hold, reject and publish are all reversible w
| Parameter | Type | Required | Notes |
|---|---|---|---|
| action | string | yes | publish | hold | reject | spam | delete |
| commentId | string | yes | id of the comment (from list_youtube_comments) |
| banAuthor | boolean | no | reject only — also auto-reject every future comment from this author |
| confirm | boolean | no | required for delete |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
List the video categories YouTube will accept on an upload in a given country. post_to_youtube takes a categoryId and this is the only way to discover a valid one — the id set AND the names differ by country, which is why regionCode is required rather than defaulted. Categories YouTube marks not-assignable are filtered out by default because an upload using one is refused. Read-only, 0 credits.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| regionCode | string | yes | ISO 3166-1 alpha-2 country code — US, GB, DE. YouTube’s category ids differ by country, so this cannot be guessed. |
| includeUnassignable | boolean | no | also return categories YouTube will refuse on upload (default false) |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
The actual search terms people typed on Google Search and Maps before this business listing appeared — the only keyword data a local business gets for free, and the direct input to their Google Ads keyword set, page titles and profile description. google_business_insights answers HOW MANY people found the listing; this answers WHICH WORDS they used. Warning: LOW-VOLUME TERMS ARE SUPPRESSED: Google
| Parameter | Type | Required | Notes |
|---|---|---|---|
| locationId | string | no | which listing, when the profile has more than one (list_business_locations) |
| months | number | no | how many whole months back, default 3, cap 12. The current month is excluded because it is always partial. |
| limit | number | no | max terms, default 100, which is also Google’s maximum |
| pageToken | string | no | nextPageToken from a previous call |
The brand's OWN recent Bluesky posts, newest first — and THIS is where the at:// AT-URI every other Bluesky tool needs comes from. bluesky_post_metrics and delete_bluesky_post both address a post by AT-URI, so without this the only way to hold one was to have just published it in the same conversation; an agent reviewing past work had no way to name anything. Each row carries the text, when it wen
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| limit | number | no | how many posts, 1–100 (default 25). 100 is Bluesky's own maximum. |
| cursor | string | no | nextCursor from a previous call — a short page is NOT end-of-feed |
| filter | string | no | posts_no_replies | posts_with_media | posts_with_replies | posts_and_author_threads |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Keyword search across the PUBLIC X index — the last 7 days. X’s own search OPERATORS work and are the point: from:nike, -is:retweet, has:media, has:links, lang:en, "exact phrase", (a OR b). Every post comes back with impressions, likes, reposts, replies, quotes and bookmarks, so you can rank what actually landed rather than what merely exists. ONLY THE LAST 7 DAYS: the full archive n
| Parameter | Type | Required | Notes |
|---|---|---|---|
| query | string | yes | the search, X operators included — e.g. (protein OR creatine) -is:retweet has:media lang:en |
| maxResults | number | no | 10–100, default 25 |
| sortOrder | string | no | default relevancy |
| startTime | string | no | ISO 8601, no earlier than 7 days ago |
| endTime | string | no | ISO 8601 end of the search window; within the last 7 days |
| nextToken | string | no | from a previous page |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
The VOLUME of a query on X over time — counts per day, hour or minute for the last 7 days, plus the total. Use it to size a topic BEFORE spending behind it, or to tell a real spike from a flat line. Cheaper than search_x because it returns numbers rather than posts: one read regardless of volume. Needs X connected.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| query | string | yes | same operator syntax as search_x |
| granularity | string | no | default day |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Trending topics on X for a place, with the post volume behind each — raw material for hook writing and for judging whether a moment is worth entering. woeid is a Yahoo Where-On-Earth id and defaults to 1 (WORLDWIDE), so it answers without one: 23424977 United States, 23424975 United Kingdom, 23424775 Canada, 23424748 Australia. A trend X returns no volume for is reported as unknown, NEVER as zer
| Parameter | Type | Required | Notes |
|---|---|---|---|
| woeid | string | no | numeric Where-On-Earth id; default 1 = worldwide |
| maxTrends | number | no | 1–50, default 25 |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
The follower or following LIST for an X account — handles, names, bios, locations and each one’s own follower/following/post counts. The COUNTS are already free on x_account and x_user; this is WHO, which is what audience analysis and creator vetting actually need. With no username it reads the CONNECTED account (“who follows us”); pass a handle to read anyone public (“who follows our competitor”)
| Parameter | Type | Required | Notes |
|---|---|---|---|
| username | string | no | handle with or without the @ — omit to read the connected account |
| direction | string | no | default followers |
| maxResults | number | no | 1–100, default 20 |
| paginationToken | string | no | next page token from the previous reply |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Hide a reply under the brand’s own X post (hidden:false to unhide) — spam and abuse moderation. A hidden reply is not deleted; it moves behind "View hidden replies". Only works on replies in a conversation the connected account started. Needs the tweet.moderate.write permission: an X connection made before 2026-10-27 must be reconnected once under Settings > Connectors > X (the tool says so withou
| Parameter | Type | Required | Notes |
|---|---|---|---|
| replyId | string | yes | the REPLY’s id, or its x.com link |
| hidden | boolean | no | default true; false unhides |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
The accounts the connected X account has BLOCKED, with handle, name, bio and follower count. READ-ONLY: X refuses the block and unblock WRITE on the access tier Hermoso's app runs on (measured 2026-09-15: X's own "attached to a Project" refusal with the scope granted), so blocking itself is done on x.com and there is no block tool here. Needs the block.read permission: an X connection made before
| Parameter | Type | Required | Notes |
|---|---|---|---|
| maxResults | number | no | 1–1000, default 100 |
| paginationToken | string | no | next page token from the previous reply |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
The connected X account's LIVE VIDEO broadcasts: past and current ones (title, state, share URL, live and total viewers, start/end) and SCHEDULED ones (start, end, whether it goes live automatically or waits for go_live). kind 'all' (default) reads both, 'live' or 'scheduled' one; pass id for a single broadcast. Read-only. Needs X connected with the broadcast permissions added 2026-09-21: an X con
| Parameter | Type | Required | Notes |
|---|---|---|---|
| kind | string | no | all (default), live or scheduled |
| id | string | no | one broadcast id (alphanumeric, up to 24 characters) |
| ids | string | no | comma-separated broadcast ids (live kind, up to 100) |
| maxResults | number | no | 1-100, default 25 |
| paginationToken | string | no | next page token from the previous reply |
| oldestStart | string | no | scheduled kind: earliest start, ISO 8601 or epoch ms |
| newestStart | string | no | scheduled kind: latest start, ISO 8601 or epoch ms |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Schedule, update, go live on, or cancel an X LIVE VIDEO broadcast on the connected account. action 'schedule' needs sourceId (from manage_x_stream_source), startAt, and manualPublish, which is REQUIRED with no default: true = nothing airs until action 'go_live'; false = X puts the brand ON AIR automatically at startAt (needs confirm:true). 'update' changes only the fields you pass (Hermoso re-send
| Parameter | Type | Required | Notes |
|---|---|---|---|
| action | string | yes | schedule, update, go_live, cancel, create, publish, end or delete |
| id | string | no | broadcast id, for every action but schedule and create |
| sourceId | string | no | stream source id from manage_x_stream_source (schedule, create) |
| startAt | string | no | ISO 8601 or epoch ms |
| endAt | string | no | ISO 8601 or epoch ms; X requires one on update |
| manualPublish | boolean | no | required on schedule: true waits for go_live, false auto-publishes at startAt |
| title | string | no | broadcast title |
| description | string | no | broadcast description |
| locale | string | no | broadcast language locale, e.g. en |
| availableForReplay | boolean | no | true = keep the broadcast available as a replay |
| isLocked | boolean | no | true = lock the broadcast (X's isLocked flag) |
| chatOption | string | no | X chat permission option (numeric string) |
| thumbnailMediaId | string | no | pre-live slate media id |
| skipPost | boolean | no | publish: true skips the post X would make announcing it |
| confirm | boolean | no | true only after the user approved this exact public or irreversible action |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
The connected X account's STREAM SOURCES, the RTMP ingest points a live broadcast plays from. action 'list' (default) and 'get' (id) show each source's id, stream key, RTMPS URL, region and whether video is arriving; 'create' (name, optional region) makes one; 'rename' (id, name); 'delete' (id, confirm:true: it kills the stream key); 'region' reads the nearest ingest region. Pass a source's id as
| Parameter | Type | Required | Notes |
|---|---|---|---|
| action | string | no | default 'list' |
| id | string | no | source id, for get, rename and delete |
| name | string | no | label, for create and rename |
| region | string | no | create only: 'atla' or 'pdxa', or an AWS region X maps onto one |
| confirm | boolean | no | true only after the user approved deleting this source |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
The chat on one of the connected X account's live broadcasts, newest first: message id, text, author handle, time and reply-to. Use it to answer viewers or find a message to moderate. Read-only. Needs X connected with the broadcast permissions added 2026-09-21: an X connection made before then must be reconnected once under Settings > Connectors > X (the tool says so without calling X). Costs cred
| Parameter | Type | Required | Notes |
|---|---|---|---|
| broadcastId | string | yes | broadcast id from list_x_broadcasts |
| maxResults | number | no | 1-200, default 100 |
| paginationToken | string | no | next page token from the previous reply |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Post ONE chat message, as the brand, into a RUNNING live broadcast on the connected X account (max 140 characters; a longer one is refused, never cut). It is PUBLIC in front of everyone watching: show the user the exact wording, get a yes, then pass confirm:true. replyTo answers one specific message. Needs X connected with the broadcast permissions added 2026-09-21: an X connection made before the
| Parameter | Type | Required | Notes |
|---|---|---|---|
| broadcastId | string | yes | running broadcast id from list_x_broadcasts |
| text | string | yes | chat message, max 140 characters; a longer one is refused |
| replyTo | string | no | chat message id to reply to |
| confirm | boolean | no | true only after the user approved this exact message; it is posted publicly |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Moderate the connected X account's live broadcast chat. action 'remove_message' (broadcastId + messageId; cannot be undone, confirm:true), 'mute' (broadcastId + userId or username; until = when a timeout ends, omit to mute indefinitely; messageId removes that message while muting), 'unmute', 'list_mutes' (everyone muted across the brand's chats), 'mute_everywhere' / 'unmute_everywhere' (userId or
| Parameter | Type | Required | Notes |
|---|---|---|---|
| action | string | yes | remove_message, mute, unmute, list_moderators, add_moderator, remove_moderator, list_mutes, mute_everywhere or unmute_everywhere |
| broadcastId | string | no | broadcast id; for remove_message, mute and unmute |
| messageId | string | no | chat message id to remove; with mute, also removes that message |
| userId | string | no | numeric X account id |
| username | string | no | X handle, resolved to the id for you |
| until | string | no | mute end, ISO 8601 or epoch ms |
| confirm | boolean | no | required for remove_message |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
The connected X account's X Chat (direct message) PRIVACY SETTINGS: who can message the brand (allowDmsFrom: all / verified / following), whether subscribers can always message it, the low-quality request filter, read receipts, and audio/video call permissions. Use it before changing them with update_x_chat_settings, or to answer 'can customers DM us on X'. Read-only. Needs X connected with direct
| Parameter | Type | Required | Notes |
|---|---|---|---|
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Change the connected X account's X Chat privacy settings: the account-wide answer to 'open our DMs to everyone' or 'turn off read receipts'. Pass only what changes. allowDmsFrom: 'all' (anyone), 'verified' (verified accounts only) or 'following' (accounts the brand follows); narrowing it stops customers the brand does not follow from messaging it, so say that before changing it. Call settings are
| Parameter | Type | Required | Notes |
|---|---|---|---|
| allowDmsFrom | string | no | who may message the brand: all, verified or following |
| alwaysAllowSubscribers | boolean | no | subscribers can always message the brand, whatever allowDmsFrom says |
| qualityFilter | boolean | no | true = low-quality message requests go to a separate folder |
| readReceipts | boolean | no | send and receive read receipts |
| callsEnabled | boolean | no | audio and video calls on or off |
| enhancedCallPrivacy | boolean | no | true = enhanced privacy for calls |
| acceptCallsFrom | object | no | who may call the brand: flags for each group below |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
The connected X account's X CHAT conversations: id (group ids start with g), type, muted, disappearing-message timer, screenshot controls, admins, members, PENDING join requests, which actions are admin-only, and the active invite link. X Chat is END-TO-END ENCRYPTED, so message text and group names cannot be read here (list_x_dms reads the plain direct-message history). A filtered page can be sho
| Parameter | Type | Required | Notes |
|---|---|---|---|
| view | string | no | default 'conversations' |
| inbox | string | no | message_requests: X's Priority tab (default) or the filtered low-quality one |
| withSenders | boolean | no | message_requests: resolve who sent each request (default true, each sender billed) |
| id | string | no | one conversation; for admin_requests, the group (g + digits) |
| maxResults | number | no | 1-100 (message_requests 1-50), default 10; every conversation or request returned is billed |
| paginationToken | string | no | nextToken from a previous call |
| withMembers | boolean | no | also resolve member/admin @handles (each one billed) |
| unreadOnly | boolean | no | only unread conversations |
| type | string | no | only 1:1 or only group conversations |
| isArchived | boolean | no | true only archived, false only not archived |
| isMuted | boolean | no | true only muted, false only not muted |
| participantIds | string | no | 1-10 numeric X account ids, comma-separated, all in the chat |
| participantQuery | string | no | name or @username prefix, 1-50 characters |
| startTime | string | no | latest message at or after this ISO 8601 time |
| endTime | string | no | latest message before this ISO 8601 time |
| inviteLink | string | no | an x.com/i/chat/group_join/... link, for view invite |
| inviteToken | string | no | the 10-character token, with id, for view invite |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Manage one of the connected X account's X Chat conversations (id from list_x_chats). For the brand only: 'mute' / 'unmute'; 'delete' removes it from the brand's inbox for good while the others keep theirs (confirm:true). For everyone in it: 'set_disappearing' (disappearAfter like '24h' or '7d'), 'clear_disappearing', 'screen_capture' (blockScreenshots OR notifyOnScreenshot, one per call). GROUPS (
| Parameter | Type | Required | Notes |
|---|---|---|---|
| action | string | yes | conversation action; the tool description lists each one and what it needs |
| id | string | yes | conversation id from list_x_chats: g + digits for a group, or the other person's numeric id for a 1:1 (accept_request: the sender's numeric id) |
| userIds | array | no | NUMERIC X account ids (add_admins, remove_admins, remove_members, reject_join_requests) |
| userId | string | no | NUMERIC X account id (approve_admin_request, reject_admin_request) |
| disappearAfter | string | no | set_disappearing: '24h', '7d', '90m' or a number of seconds |
| blockScreenshots | boolean | no | screen_capture: true = block screenshots of the chat |
| notifyOnScreenshot | boolean | no | screen_capture: true = notify everyone when someone takes a screenshot |
| adminOnly | object | no | admin_settings: true = only admins may |
| title | string | no | enable_invite: the group name shown on the link preview (stored unencrypted) |
| avatarUrl | string | no | enable_invite: https image for the preview |
| welcomeMessage | string | no | enable_invite: welcome text on the invite preview; stored unencrypted |
| expiresAt | string | no | enable_invite: ISO 8601 or epoch ms; omit for a link that does not expire |
| confirm | boolean | no | true only after the user approved this exact delete / leave / removal / link shutdown |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
ACCOUNT-LEVEL numbers for ANY public X account by handle — followers, following, posts, listed and media counts, plus bio, location, verified status and account age. The research twin of x_account (which reads YOUR connected account): use this to size a competitor or vet a creator before working with them. Returns the identical shape as x_account, so the two are never described differently, and
| Parameter | Type | Required | Notes |
|---|---|---|---|
| username | string | yes | the handle, with or without the @ |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
ACCOUNT-LEVEL numbers for the connected X account — FOLLOWERS, following, total posts, listed count, media count — plus display name, bio, location, verified status and when the account was created. This is the tool for “how many followers do we have on X”, “is our X growing”, or to confirm whose account is linked before posting; for a single post use x_post_metrics or x_post_insights instead. A c
| Parameter | Type | Required | Notes |
|---|---|---|---|
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Change the TEXT or CAPTION of a message already posted to a Telegram channel, group or chat — the fix for a typo that would otherwise cost the post AND its accumulated views, since deleting and reposting starts from zero. Pass text for a plain post or caption for one carrying media; Telegram refuses the wrong one and its error names the other. It overwrites the published text, and the previous
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| chatId | string | yes | the channel’s @username or numeric chat id |
| messageId | number | yes | from post_to_telegram, or the number at the end of a t.me link |
| text | string | no | the new text, for a post with no media |
| caption | string | no | the new caption, for a post carrying a photo/video/album |
| linkPreview | boolean | no | false to suppress the link preview |
| parseMode | string | no | 'HTML' to format the new text with Telegram's HTML tags; omit for plain text |
| buttons | array | no | URL buttons under the post. An edit REPLACES the buttons with what it sends, so pass them again to keep them; the reply reads back the buttons Telegram now shows. |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Pin a message to the top of a Telegram channel or group, or unpin one. A channel’s pinned message is its most valuable real estate and its de-facto call to action. Pinning is SILENT by default (no member notification) — pass notify:true to ping. To unpin, pass unpin:true; omitting messageId then unpins the most recently pinned message, which is what “unpin” usually means. The bot must be an admini
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| chatId | string | yes | the channel’s @username or numeric chat id |
| messageId | number | no | required to PIN; optional when unpinning |
| unpin | boolean | no | true to unpin instead of pin |
| notify | boolean | no | true to notify members; default silent |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Create a NAMED invite link to a Telegram channel or group, optionally with an expiry, a member cap, or join approval. THIS IS THE ONLY SUBSCRIBER ATTRIBUTION TELEGRAM OFFERS: the Bot API exposes no per-post views and no post history, so “which campaign brought these subscribers” is otherwise unanswerable — give each campaign its own named link and Telegram reports how many joined through each. A m
| Parameter | Type | Required | Notes |
|---|---|---|---|
| chatId | string | yes | the channel’s @username or numeric chat id |
| name | string | no | what this link is for — the campaign name; up to 32 characters, and the whole point of the tool |
| expiresAt | string | no | ISO 8601 timestamp after which the link stops working |
| memberLimit | number | no | how many people may join through it; cannot be combined with requireApproval |
| requireApproval | boolean | no | each join waits for an admin; cannot be combined with memberLimit |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Create an event on a LinkedIn company Page the brand administers: an EXTERNAL online event (a webinar on Zoom, YouTube or your own site — pass url), an IN-PERSON event (pass address), or a LinkedIn Live event (format:"live" — that one needs LinkedIn Live access on the member, and without it LinkedIn refuses with “The member does not have LINKEDIN_LIVE creation access”, measured 2026-08-31). Option
| Parameter | Type | Required | Notes |
|---|---|---|---|
| name | string | yes | the event title |
| startsAt | string | yes | ISO 8601, must be in the future — LinkedIn rejects an event that has already begun |
| endsAt | string | no | ISO 8601, after startsAt; defaults to one hour after the start |
| description | string | no | event description shown on the event page |
| organizationId | string | no | the Page — omit if only one is shared with this profile |
| format | string | no | inferred from url / address when omitted; live needs LinkedIn Live access |
| url | string | no | external: where it streams or where people join; in_person: an info page |
| address | object | no | in_person venue |
| venueDetails | string | no | room, floor, parking… |
| registrationForm | boolean | no | attach a LinkedIn-built registration form (needs privacyPolicyUrl) |
| privacyPolicyUrl | string | no | privacy policy link; required with registrationForm:true |
| leadFormId | string | no | or attach your own APPROVED lead form instead of registrationForm |
| discoveryMode | string | no | LISTED (shown in event search) or URL_ONLY (reachable by link only) |
| announce | boolean | no | ALSO publish the Page post that shares the event. LinkedIn makes an event reachable only once a post shares it (an unannounced one stopped being readable within a minute, measured 2026-10-27). PUBLIC |
| announceText | string | no | the copy of the announcing post |
Edit a LinkedIn event the Page organizes: name, description, start / end time, the join or info url, the in-person address or venue details, or discoveryMode (LISTED / URL_ONLY). LinkedIn’s own rules, quoted back when it refuses: an event can be edited only BEFORE it starts (never once cancelled), and online vs in-person never changes. Read back from LinkedIn. 0 credits.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| eventId | string | yes | from list_linkedin_events or create_linkedin_event |
| organizationId | string | no | Page id that organizes the event; omit when only one is shared |
| name | string | no | new event title |
| description | string | no | new event description |
| startsAt | string | no | ISO 8601 |
| endsAt | string | no | ISO 8601 |
| url | string | no | join or info link for an online or external event |
| address | object | no | in-person venue address: {line1, city, region, postalCode, country} |
| venueDetails | string | no | room, floor or parking notes for an in-person event |
| discoveryMode | string | no | LISTED or URL_ONLY (see create_linkedin_event) |
The events a LinkedIn company Page is organizing — name, link, start and end times, online or in-person, visibility and lifecycle state. LinkedIn is the one channel where an event is a first-class object rather than a post about one, so this is how you see what a B2B brand actually has scheduled (webinars, launches, live sessions) and pair it with the posts promoting it. Only Pages the user has SH
| Parameter | Type | Required | Notes |
|---|---|---|---|
| organizationId | string | no | the Page to read — omit if only one Page is shared with this profile; list_linkedin_pages shows them |
| start | number | no | pagination offset; default 0 |
| count | number | no | max 50, default 10 |
Search the ads running on LinkedIn, from LinkedIn’s OWN Ad Library, and get three things LinkedIn publishes for each ad: the advertiser’s actual TARGETING (location, language, audience, company, job, age, gender, interests, traits — both the segments they INCLUDED and the ones they EXCLUDED), estimated impressions as a range plus the percentage split BY COUNTRY, and, for ads LinkedIn REMOVED, the
| Parameter | Type | Required | Notes |
|---|---|---|---|
| keyword | string | no | searched in the ad’s content; several words are a logical AND, not a phrase |
| advertiser | string | no | advertiser name, e.g. Notion |
| payerName | string | no | who PAID for the ad, which is often a different entity from the advertiser |
| countries | string | no | comma-separated ISO codes the ad was served in, e.g. US,GB |
| dateFrom | string | no | YYYY-MM-DD, inclusive — must be given together with dateTo |
| dateTo | string | no | YYYY-MM-DD, EXCLUSIVE — must be given together with dateFrom |
| includeFacets | string | no | only ads whose targeting INCLUDES these facet categories: LANGUAGE, LOCATION, AUDIENCE, AGE, GENDER, COMPANY, EDUCATION, JOB, INTERESTS, TRAITS |
| excludeFacets | string | no | only ads whose targeting EXCLUDES these facet categories |
| impressionsFrom | number | no | lower bound of the estimated impressions range; pass both bounds or neither |
| impressionsTo | number | no | upper bound; pass both bounds or neither |
| sort | string | no | by creation time; default DESCENDING (newest first) |
| start | number | no | pagination offset; default 0 |
| count | number | no | max 25 |
Search ORGANIC LinkedIn posts that creators have themselves disclosed as brand partnerships — paid influencer content, not ads bought through Campaign Manager. Use it to see which creators a competitor is paying, what a sponsored post in your category actually looks like, or to vet a creator’s existing brand relationships before approaching them. DIFFERENT CONTRACT from the ad search, so do not ca
| Parameter | Type | Required | Notes |
|---|---|---|---|
| keyword | string | yes | REQUIRED on this finder, unlike the ad search — searched in the post content; several words are a logical AND |
| dateRange | string | no | an ENUM, not a date range; omit to search everything LinkedIn still holds |
| start | number | no | pagination offset; default 0 |
| count | number | no | max 25 |
Google’s CATEGORY vocabulary for a country — the primary category is a required field on a Business Profile and one of the strongest LOCAL RANKING signals, and Google accepts only its own stable ids (categories/gcid:coffee_shop), never a display name. So this is the lookup you run BEFORE setting a category, and filter searches display names (“pizza”, “dentist”). regionCode is REQUIRED because
| Parameter | Type | Required | Notes |
|---|---|---|---|
| regionCode | string | yes | ISO 3166-1 alpha-2 country code — US, GB, CA, AU |
| languageCode | string | no | BCP 47, default en |
| view | string | no | FULL adds service types and valid attributes |
| filter | string | no | search the display names, e.g. “coffee” |
| pageSize | number | no | categories per page |
| pageToken | string | no | next page token from the previous reply |
The ATTRIBUTES a Business Profile can set — accessibility, amenities, service options, payment types — with the valid values for each. These are what local searchers FILTER on, so a listing with none set is invisible to those filters. Scope it exactly ONE of three ways and no more: locationName (locations/123) for what THAT listing may set, categoryName (categories/gcid:…, from list_business_c
| Parameter | Type | Required | Notes |
|---|---|---|---|
| locationName | string | no | locations/{id} — from list_business_locations |
| categoryName | string | no | categories/gcid:… — from list_business_categories |
| regionCode | string | no | ISO 3166-1 alpha-2 country code |
| languageCode | string | no | BCP 47 language code; default en |
| showAll | boolean | no | true = send Google's showAll flag for the full attribute list |
| pageSize | number | no | attributes per page; default 200 |
| pageToken | string | no | next page token from the previous reply |
What GOOGLE thinks a Business Profile should say — the pending edits it has inferred from the web, from user submissions and from its own sources, with changedFields naming exactly which fields it would change. THESE CAN GO LIVE IF NOBODY LOOKS, which is why a business needs to see them. An empty result is a real and good answer (Google is proposing nothing) and is reported as such, never as a f
| Parameter | Type | Required | Notes |
|---|---|---|---|
| locationName | string | yes | locations/{id} — from list_business_locations |
ACCOUNT-LEVEL numbers for a Telegram channel, group or chat — its SUBSCRIBER / MEMBER COUNT — plus title, @username, description, invite link and linked discussion group. This is the answer to “how many subscribers does our Telegram channel have”. WHICH CHAT IS ALWAYS ASKED FOR: pass chatId (a public channel’s @username, or the numeric id) — the Bot API publishes no method that lists the chats a b
| Parameter | Type | Required | Notes |
|---|---|---|---|
| chatId | string | yes | the channel’s @username or numeric chat id — required; there is no default |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
ACCOUNT-LEVEL numbers for a Bluesky account — FOLLOWERS, following and total posts — plus display name, bio, avatar and when it was created. With no argument it reads the CONNECTED account, which is the answer to “how many followers do we have on Bluesky”; pass actor (a handle or DID) to read any other account, which is free and is how you size a competitor there. The reply says which one it rea
| Parameter | Type | Required | Notes |
|---|---|---|---|
| actor | string | no | a Bluesky handle (hermoso.ai or @hermoso.ai) or a did:plc:… — omit it to read the connected account |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Read live engagement for up to 25 of the connected account’s Bluesky posts — likes, reposts, replies, quotes and bookmarks. Address a post by its AT-URI (the at://… value post_to_bluesky returns), not its web URL. Bluesky publishes NO impression or view count in any AT Protocol lexicon, so these are COUNTS with no denominator and no engagement rate can be computed from them — do not present one.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| uris | array | yes | AT-URIs of the posts, at most 25 (Bluesky’s own maximum for one call) |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Read the connected Bluesky account’s DM conversations — who each one is with, how many messages are unread, and whether it is a REQUEST (someone the account does not follow, which Bluesky holds separately, the same idea as a message request). Free, no credits, no vendor approval: AT Protocol app passwords are not scoped. It DOES need a PRIVILEGED app password — one created with direct-message acce
| Parameter | Type | Required | Notes |
|---|---|---|---|
| limit | number | no | how many conversations, 1–100 (default 25) |
| cursor | string | no | walk further back — pass the cursor from a previous call |
| readState | string | no | only conversations with unread messages |
| status | string | no | "request" = from someone this account does not follow |
| kind | string | no | direct or group conversations only |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Read a Bluesky DM conversation in CHRONOLOGICAL order (Bluesky returns newest-first on the wire; this reverses it so the conversation reads top to bottom). Each message says whether it was sent by this account. A message with no text is reported as DELETED rather than as an empty message — “they unsent it” and “they sent nothing” are different facts. Get a convoId from list_bluesky_convos.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| convoId | string | yes | from list_bluesky_convos |
| limit | number | no | how many messages, 1–100 (default 30) |
| cursor | string | no | walk further back through older messages |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Send ONE direct message on the connected Bluesky account, either into an existing conversation (convoId) or to a named handle (a conversation is opened for you — the handle is resolved to a DID first, because Bluesky identifies chat members by DID). A DM allows 1000 characters, NOT the 300 a post allows. Refusals name the real situation rather than a generic failure: the recipient has DMs off, onl
| Parameter | Type | Required | Notes |
|---|---|---|---|
| convoId | string | no | reply into an existing conversation (from list_bluesky_convos). Omit to start one with to. |
| to | string | no | the recipient’s Bluesky handle, e.g. alice.bsky.social. Ignored when convoId is given. |
| text | string | yes | the message, up to 1000 characters |
| replyToMessageId | string | no | reply to a specific message in the conversation |
| dryRun | boolean | no | CHECK FIRST, SEND NOTHING. Asks Bluesky whether a DM to to would be delivered at all and returns canChat plus the reason when it would not — that account has DMs off, only accepts them from people i |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Clear the unread count on Bluesky DMs — ONE conversation, or the WHOLE ACCOUNT when convoId is omitted (Bluesky’s own mark-all, narrowable with status to just the message requests or just the accepted threads). Useful after triaging an inbox so the next list_bluesky_convos does not surface the same threads again. Reports the unread count — or the number of conversations — Bluesky reads back, not t
| Parameter | Type | Required | Notes |
|---|---|---|---|
| convoId | string | no | from list_bluesky_convos. OMIT to mark EVERY conversation on the account read. |
| messageId | string | no | mark read only up to this message; omit to clear the whole conversation |
| status | string | no | mark-all only: narrow it to just the message requests, or just the accepted conversations. Omit to clear both. An unknown value is refused rather than dropped — a dropped one would clear everything wh |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Act on one Bluesky DM conversation. ACCEPT a message request — Bluesky holds DMs from people the account does not follow in a separate requests folder, and until one is accepted it stays there. MUTE / UNMUTE it. LOCK / UNLOCK it (no new messages). LEAVE it. Or DELETE one message from THIS account’s view. TWO THINGS TO SAY OUT LOUD BEFORE USING THEM: deleting is FOR SELF ONLY — the other person sti
| Parameter | Type | Required | Notes |
|---|---|---|---|
| action | string | yes | what to do to the conversation |
| convoId | string | yes | from list_bluesky_convos |
| messageId | string | no | required for deleteMessage — from read_bluesky_dm |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Add or remove an emoji reaction on one message in a Bluesky DM — the light acknowledgement that does not need a written reply, and the thing to reach for when someone says “thanks” and a paragraph back would be worse than a thumbs-up. A REACTION IS EXACTLY ONE EMOJI: that is Bluesky’s own rule, so a word or a phrase is refused here rather than by the service (where it comes back as an opaque React
| Parameter | Type | Required | Notes |
|---|---|---|---|
| convoId | string | yes | from list_bluesky_convos |
| messageId | string | yes | from read_bluesky_dm |
| value | string | yes | exactly one emoji character, e.g. the thumbs-up emoji |
| remove | boolean | no | true to take the reaction off instead of putting it on |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Read the connected TikTok creator’s REAL posting options BEFORE posting: which privacy levels THEY are allowed to use, whether comments / duet / stitch are available on their account, their maximum video length, and their nickname. TikTok REQUIRES that the user is shown these actual options and picks a privacy level — never assume or default one. Call this first, show the options, get the user’s p
| Parameter | Type | Required | Notes |
|---|---|---|---|
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
Use this when the user wants to post a video or photo post to TikTok now. Publishing costs no credits. Publish to the user’s connected TikTok account — a finished VIDEO, or a PHOTO POST (TikTok’s photo/slideshow format). A photo post carries 1 to 35 images and ONE image is simply a one-slide photo post, so there is nothing special to do for a single picture: pass imageUrls, in the order the slides
| Parameter | Type | Required | Notes |
|---|---|---|---|
| account | string | no | WHICH connected account of this channel to post as — its @handle or id from list_connector_accounts. Needed only when the profile has more than one tiktok account connected (several and none named is |
| brand | string | no | WHICH PROFILE this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no profile, or two, is R |
| hook | string | no | WHAT ANGLE this post is built on, recorded only at publish time. Omit it and this post never votes on which hook works. |
| subject | string | no | WHAT THIS POST IS ABOUT — product, feature, offer or theme (e.g. "winter coat", "free trial"). post_performance's second grouping axis: reuse the exact wording, as with hook. |
| recipe | string | no | the post's FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format |
| ideaId | string | no | short id of the content-plan idea this post came from |
| videoUrl | string | no | the video to post — a Hermoso render URL or an upload_file url. Omit for a photo post; a URL, not a local file path; use upload_file for local files |
| imageUrls | array | no | a PHOTO POST: 1–35 image URLs in slide order. One url = a single-image photo post. Do not combine with videoUrl. |
| coverIndex | number | no | photo posts: which slide is the cover, 0-based. Default 0 (the first slide). |
| destination | string | no | "post" (default) = live on the profile now (needs the privacy the user chose + their explicit yes); "draft" = to their TikTok inbox for them to finish and publish in the app — only when they ask for a |
| title | string | no | the caption — hashtags go here (video ≤2200 chars, photo post ≤4000) |
| photoTitle | string | no | photo posts only: a short title above the caption (≤90 chars). Defaults to the caption’s first line. |
| privacy | string | no | REQUIRED for destination:"post", for photos and video alike. Must be one the creator actually allows — read them from tiktok_creator_info, never guess. |
| disableComment | boolean | no | true = turn comments off on this post |
| disableDuet | boolean | no | video only — TikTok has no duet on a photo post |
| disableStitch | boolean | no | video only — TikTok has no stitch on a photo post |
| autoAddMusic | boolean | no | photo posts only: let TikTok add a recommended track (default true — a silent slideshow reads as broken) |
| coverTimestampMs | number | no | video only: which frame to use as the cover, in ms |
| aiGenerated | boolean | no | TikTok’s is_aigc AI-generated-content label (video posts). OMIT IT and Hermoso decides from provenance: a Hermoso render is declared, a video that came through upload_file or an external URL (the user |
| brandedContent | boolean | no | discloses a paid partnership — cannot be combined with SELF_ONLY privacy |
Follow a TikTok post that post_to_tiktok sent but TikTok was still processing (it came back pending:true). Pass the publishId it returned. Answers one of three states: done (PUBLISH_COMPLETE, with the public url once TikTok’s moderation has named it; urlStatus is ready, pending, private or draft), failed (with TikTok’s reason), or still processing, which is normal for a few minutes and is NOT
| Parameter | Type | Required | Notes |
|---|---|---|---|
| publishId | string | yes | the publishId post_to_tiktok returned |
| account | string | no | which connected TikTok account the post went out on (@handle or id from list_connector_accounts) when the profile has more than one |
Read the connected TikTok account: display name, username, bio, verified status, and their follower / following / total-likes / video counts. Use it for “how many followers do we have on TikTok”, “how is our TikTok doing”, or to confirm whose account is linked before posting. Read-only. Needs TikTok connected (Settings > Connectors > TikTok).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
The connected account’s own TikTok posts with per-video stats — views, likes, comments, shares, duration, cover image and link. TWO WAYS TO ASK: with no arguments it lists the most recent (newest first, up to 20 a page); with videoIds it reads THOSE posts directly however old they are, which is how you answer "how did that specific video do" without paging back through the account. Any id TikTok d
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| limit | number | no | 1-20, default 10 (ignored when videoIds is given) |
| videoIds | array | no | read these specific TikTok video ids instead of listing recent ones — up to 20 per call |
| account | string | no | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
The DM threads on the brand’s Facebook Page and its Instagram account — who wrote, when they last wrote, how many messages and how many are unread. THIS IS WHERE THE BUYING QUESTION GETS ASKED. IT READS THE INBOX AND THE MESSAGE REQUESTS BY DEFAULT (Meta’s "other" folder), which is where a DM from someone who does NOT follow the account lands — every thread says which of the two it came from, beca
| Parameter | Type | Required | Notes |
|---|---|---|---|
| platform | string | no | omit to read both — Messenger and Instagram Direct are separate inboxes |
| folder | string | no | narrow to ONE folder. Omit to read inbox + other, which is almost always what you want. "other" IS the message-requests folder. A folder Meta does not recognise is REFUSED rather than forwarded, becau |
| limit | number | no | threads per surface (1–100, default 25) |
| account | string | no | which Instagram account — an @handle or id from list_connector_accounts("instagram"). The way a DIRECT (Instagram Login) account is reached; omit for the Page-linked one. |
| pageId | string | no | Facebook Page id — omit when only one Page is connected |
The messages in one DM thread, oldest to newest, each marked inbound or outbound — plus WHETHER A REPLY IS ALLOWED RIGHT NOW. Meta only permits a reply within 24 hours of the person acting, and the window is computed from their LAST MESSAGE rather than from the thread’s updated time (which includes our own replies and would report the window open for a day after WE spoke). Read this before draftin
| Parameter | Type | Required | Notes |
|---|---|---|---|
| conversationId | string | yes | from list_meta_conversations |
| platform | string | no | so the Instagram 20-message ceiling can be stated when it applies |
| limit | number | no | messages (1–100, default 25) |
| account | string | no | which Instagram account — an @handle or id from list_connector_accounts("instagram"). The way a DIRECT (Instagram Login) account is reached; omit for the Page-linked one. |
| pageId | string | no | Facebook Page that owns the DM thread; omit when only one Page is connected |
Send a text reply to someone who has messaged the brand. THIS REACHES A REAL PERSON — show the user the exact text and who it goes to, get a yes, then send. Pass conversationId as well as recipientId and Hermoso checks Meta’s 24-hour window for free BEFORE sending, and refuses with the real reason instead of letting Meta refuse it; without one it sends and discloses that the window could not b
| Parameter | Type | Required | Notes |
|---|---|---|---|
| recipientId | string | yes | the PSID (Messenger) or IGSID (Instagram) to reply to — replyTo from read_meta_conversation |
| text | string | yes | the reply |
| humanAgent | boolean | no | HUMAN AGENT — true when a PERSON on the team is personally answering a customer's question more than 24 hours after they last wrote (up to 7 days): Meta's HUMAN_AGENT tag. Never for automated, promoti |
| conversationId | string | no | strongly recommended: it is what lets the 24-hour window be checked BEFORE sending rather than discovered by a refusal |
| platform | string | no | MESSENGER or INSTAGRAM; the channel the conversation is on |
| account | string | no | which Instagram account — an @handle or id from list_connector_accounts("instagram"). The way a DIRECT (Instagram Login) account is reached; omit for the Page-linked one. |
| pageId | string | no | Facebook Page that owns the conversation; omit when only one Page is connected |
Have Meta PUSH events to Hermoso instead of Hermoso polling for them. Subscribe a Page to feed (comments, posts, likes and shares — this is what makes a new comment ARRIVE rather than be polled for), mention (someone mentions the Page), leadgen (a lead-ad form submission delivered the moment it happens, which turns "read the leads when asked" into "the leads arrive"), and messages (an inbo
| Parameter | Type | Required | Notes |
|---|---|---|---|
| fields | array | yes | what to be told about. marketing_messages is shorthand for the five marketing_message_* outcome fields (sent / delivered / read / clicked / failed of a paid Messenger marketing message, read with ge |
| pageId | string | no | Facebook Page id — omit when only one Page is connected |
What Meta ACTUALLY has recorded for this app on a Page — the fields it will push, read straight back from Meta rather than from anything we stored. CHECK THIS BEFORE CONCLUDING THAT WEBHOOKS ARE NOT WORKING: an unsubscribed Page is completely silent and looks identical to a quiet one, so an empty event feed is not evidence of anything until you have seen the subscription. Read-only, 0 credits.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| pageId | string | no | Facebook Page id — omit when only one Page is connected |
Remove this app’s webhook subscription from a Page, so Meta stops pushing its events. It removes the WHOLE subscription — Meta’s DELETE takes no field list — so it is all fields or none; to keep some, call subscribe_meta_webhooks again with just those. Nothing published or posted is affected; only the notifications stop. THE ANSWER IS THE READ-BACK: a subscription still present when read back mean
| Parameter | Type | Required | Notes |
|---|---|---|---|
| pageId | string | no | Facebook Page id — omit when only one Page is connected |
The Page events Meta has PUSHED to Hermoso — new comments, mentions, lead-form submissions and inbound Messenger/Instagram DMs — each with the moment it arrived. This is the difference between a brand learning about a comment when someone thinks to look and learning about it when it happens. Scoped to the Pages this profile can actually see, so it never shows another account’s activity. AN EMPTY F
| Parameter | Type | Required | Notes |
|---|---|---|---|
| pageId | string | no | narrow to one Page — only within the Pages this profile already administers; naming an id you cannot see does not widen the answer |
| field | string | no | narrow to one kind: feed, mention, leadgen or messages |
| limit | number | no | how many, newest first (default 50) |
The brand's Instagram DM automations (comment keyword -> DM, DM keyword, story reply, ig.me ref), each with its stats, plus the recent send log and the connected Instagram accounts. Pass id for one rule's log. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| id | string | no | one rule's id; shows only that rule's send log |
| limit | number | no | log rows, newest first (default 30) |
Create (no id) or edit (id) an automation that DMs people automatically: a comment with a keyword on one post / the next post / any post gets a private reply, or a DM / story reply / ig.me link gets an answer. Optional link buttons, public comment replies, follow gate and dry run. Once per person per post; Meta allows private replies within 7 days of the comment. It DMs real people, so confirm the
| Parameter | Type | Required | Notes |
|---|---|---|---|
| id | string | no | rule id to edit; omit to create a new automation |
| name | string | no | name of the automation, shown in the list |
| account | string | no | @handle or id of the Instagram account; omit when only one is connected |
| trigger | object | no | what starts the rule: {type, keywords, match, scope, ...}; keywords are case-insensitive |
| message | object | no | what the person receives: {text, buttons}; up to 3 link buttons |
| publicReplies | array | no | comment rules: public replies under the comment, rotated (e.g. "Sent! Check your DMs") |
| followGate | object | no | send the content only to followers: they tap a button first |
| cooldownHours | number | no | DM/story/ref rules: answer one person at most once per this many hours (default 24) |
| enabled | boolean | no | false pauses the automation without deleting it |
| dryRun | boolean | no | log what would be sent without sending |
Check whether a sample comment or DM would trigger a saved rule and exactly what it would send. Sends nothing. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| id | string | yes | saved rule id to test against |
| text | string | yes | the sample comment or DM |
Delete one automation by id. Nothing further is sent by it; DMs already sent stay. To pause instead, save it with enabled:false. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| id | string | yes | rule id to delete; messages already sent stay sent |
The brand's DM automations on Instagram, Facebook (Page comments, ads, Messenger) and X, each with its stats, the recent send log and the connected accounts per channel. Pass channel to narrow, id for one rule's log. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| channel | string | no | instagram, facebook or x; omit for all channels |
| id | string | no | one rule's id; shows only that rule's send log |
| limit | number | no | log rows, newest first (default 30) |
Create (no id) or edit (id) an AUTO-DM / AUTO-REPLY automation: "when someone comments GUIDE on my posts, DM them my guide link", "reply to anyone who DMs PRICE with our pricing page", "reply to people who mention us on X". PAUSE one with {id, enabled:false}; PRACTICE MODE (dryRun:true) logs what it would send and sends nothing. Instagram/Facebook: a keyword comment (one post, the next post, any p
| Parameter | Type | Required | Notes |
|---|---|---|---|
| id | string | no | rule id to edit; omit to create a new automation |
| channel | string | no | instagram (default), facebook (a Facebook Page: comments on its posts and ads, plus Messenger) or x |
| name | string | no | name of the automation, shown in the list |
| account | string | no | the account on that channel: @handle (Instagram, X) or Page name/id (Facebook); omit when only one is connected |
| trigger | object | no | what starts the rule: {type, keywords, match, scope, mediaId, ...}; keywords are case-insensitive |
| message | object | no | what the person receives: {text, buttons}; on X the text is the public reply, up to 280 characters |
| publicReplies | array | no | Instagram/Facebook comment rules: public replies under the comment, rotated |
| followGate | object | no | Instagram only: followers get the content after tapping a button |
| cooldownHours | number | no | DM/ref rules: answer one person at most once per this many hours (default 24) |
| enabled | boolean | no | false pauses the automation without deleting it |
| dryRun | boolean | no | log what would be sent without sending |
Check whether a sample comment, mention or DM would trigger a rule (saved by id, or unsaved) and exactly what it would send on which channel; on X it also quotes what X charges for the send. Sends nothing. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| id | string | no | saved rule id to test; omit to test a rule that is not saved yet |
| text | string | yes | the sample comment, mention or DM |
| channel | string | no | instagram (default), facebook (a Facebook Page: comments on its posts and ads, plus Messenger) or x |
| name | string | no | name of the automation, shown in the list |
| account | string | no | the account on that channel: @handle (Instagram, X) or Page name/id (Facebook); omit when only one is connected |
| trigger | object | no | what starts the rule: {type, keywords, match, scope, mediaId, ...}; keywords are case-insensitive |
| message | object | no | what the person receives: {text, buttons}; on X the text is the public reply, up to 280 characters |
| publicReplies | array | no | Instagram/Facebook comment rules: public replies under the comment, rotated |
| followGate | object | no | Instagram only: followers get the content after tapping a button |
| cooldownHours | number | no | DM/ref rules: answer one person at most once per this many hours (default 24) |
| enabled | boolean | no | false pauses the automation without deleting it |
| dryRun | boolean | no | log what would be sent without sending |
Delete one automation by id on any channel. Nothing further is sent by it; messages already sent stay; on X the event subscription is removed when no rule needs it. To pause instead, save it with enabled:false. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| id | string | yes | rule id to delete, on any channel; messages already sent stay sent |
The creators this brand has set up for PARTNERSHIP ADS — ads that run from a CREATOR’s handle instead of the brand’s, which is what a paid collaboration looks like in feed. TWO SEPARATE LISTS come back and NEITHER IMPLIES THE OTHER: the AD-PERMISSION list is whose content this profile may run as an advert, and the TAG-APPROVAL list is who may tag this brand as a paid partner on their own organic p
| Parameter | Type | Required | Notes |
|---|---|---|---|
| creatorUsername | string | no | narrow the AD-PERMISSION list to one creator handle. It does not affect the tag-approval list, which is addressed by numeric id only. |
| creatorIds | string | no | comma-separated NUMERIC Instagram account ids to CHECK against the TAG-APPROVAL list. Meta publishes no way to list that edge — user_ids is required on it and the response is filtered to exactly the |
| pageId | string | no | Facebook Page id — omit when only one Page is connected. Both lists live on its linked Instagram account. |
Add a creator to — or remove one from — this brand’s Partnership Ads lists. Name at least one list: allowAds (whose content this profile may RUN AS AN ADVERT) and/or allowTagging (who may TAG this brand as a paid partner on their own post). NEITHER DEFAULTS ON, because one of them lets somebody attach this brand’s name to their own post. ADDING IS A REQUEST, NOT AN APPROVAL: Meta’s own name fo
| Parameter | Type | Required | Notes |
|---|---|---|---|
| creator | string | yes | the creator’s Instagram @username, or their numeric Instagram account id. The ad-permission list accepts either; the tag-approval list takes numeric ids ONLY, because Meta publishes no username form f |
| creatorIds | array | no | for allowTagging, which accepts several NUMERIC ids at once. A handle here is refused by name rather than sent and rejected by Meta — run instagram_profile on each handle first and pass the ids it r |
| allowAds | boolean | no | the Partnership Ads permission list — whose content this profile may run as an advert |
| allowTagging | boolean | no | the tag-approval list — who may tag this brand as a paid partner on their own organic post |
| remove | boolean | no | revoke instead of add |
| pageId | string | no | Facebook Page id — omit when only one Page is connected |
Turn an Instagram @handle into the NUMERIC Instagram account id, straight from Meta — with the account’s real name, bio, follower count, post count, website and profile picture. Two jobs: it answers "what is @<handle>’s Instagram id?", and it sizes up any brand or creator on Instagram before you work with them (followers and post count are Meta’s own numbers, not a scraper’s). CALL IT BEFORE manag
| Parameter | Type | Required | Notes |
|---|---|---|---|
| handle | string | yes | the Instagram handle — "fellowproducts", "@fellowproducts" or an instagram.com profile link all work. Meta matches it EXACTLY and does not strip a leading @, so Hermoso normalises it first. |
| pageId | string | no | Facebook Page id — omit when only one Page is connected. Meta requires the lookup to be made AS a professional account, so the Page must have an Instagram account linked. |
| posts | integer | no | how many of the account’s recent posts to read with like / comment / view counts and links (0–50, default 0). First-party and free |
| cursor | string | no | next page of posts — the cursor from the previous reply |
Whether this Instagram account can tag products at all, and which catalogs its SHOP can tag from. CALL THIS FIRST: product tagging needs an APPROVED INSTAGRAM SHOP, and if the account does not have one, tagging fails AFTER the photo is already uploaded. The reply says which of three things is true — eligible, not eligible (a Commerce Manager approval nothing in Hermoso can grant, and not a sign an
| Parameter | Type | Required | Notes |
|---|---|---|---|
| pageId | string | no | Facebook Page id — omit when only one Page is connected. Its linked Instagram account is the one that gets tagged. |
The products in one catalog that can actually be TAGGED on an Instagram post — this is where the product_id for a product tag comes from. Omit q to see everything tag-eligible; pass a product name or SKU to narrow it. THIS IS A SMALLER SET THAN THE CATALOG HOLDS: a product can be in the catalog, counted by list_meta_catalog_products, and still not be taggable, so an empty answer here is never ev
| Parameter | Type | Required | Notes |
|---|---|---|---|
| catalogId | string | yes | from list_instagram_shopping_catalogs. Meta REQUIRES it — there is no search-every-catalog form. |
| q | string | no | product name or SKU. Omit it to list every tag-eligible product, which is a real ask rather than a missing argument. |
| pageId | string | no | Facebook Page id — omit when only one Page is connected |
READ the product tags on a post the brand has already published, or ADD/MOVE tags on it. Pass tags to update: Meta's own behaviour is "updates coordinates if the product is already tagged; otherwise adds new tag" — so it is ADD-OR-MOVE, never replace-all, and it CANNOT be used to take a tag off. THERE IS NO WAY TO REMOVE A PRODUCT TAG: Meta documents Creating, Reading and Updating on this edge a
| Parameter | Type | Required | Notes |
|---|---|---|---|
| mediaId | string | yes | the numeric Instagram media id, from list_instagram_media |
| tags | array | no | ADD or MOVE these tags. Omit to just read. ON A PHOTO each tag is {product_id, x, y}: x and y are FRACTIONS of the image, 0.0 (left/top) to 1.0 (right/bottom), 0.5,0.5 is the middle, and both are requ |
| pageId | string | no | Facebook Page id — omit when only one Page is connected |
The PRODUCT CATALOGS this Meta login can advertise from — id, name, how many products each holds, and which business portfolio owns it, WITH THAT PORTFOLIO’S NUMERIC ID. It also lists every business portfolio this login administers (portfolios), whether or not it holds a catalog yet — so this is where the businessId create_meta_catalog needs comes from, and the two tools chain with no trip to Bu
No parameter schema on file.
The PRODUCT SETS inside one catalog — a set is a slice of the catalog ("summer dresses", "under $50", "everything"), and it is what an AD SET actually advertises. Meta normally auto-creates an "all products" set with every catalog, so an empty list points at the catalog rather than at the request. A set holding ZERO products binds without error and then serves nothing — the ad runs, spends and sho
| Parameter | Type | Required | Notes |
|---|---|---|---|
| catalogId | string | yes | the numeric catalog id from list_meta_catalogs (a catalog NAME is not an id) |
| limit | number | no | max rows (1–100, default 50) |
A page of the actual PRODUCTS in a catalog — name, retailer id, price, availability, image and, most importantly, Meta’s REVIEW STATUS for each. A REJECTED product stays in the catalog, is counted by the catalog’s catalog.product_count field, and will never be shown in an ad — so this is how you find out why a catalog ad is serving less than the merchant expects. Use it to confirm a catalog really
| Parameter | Type | Required | Notes |
|---|---|---|---|
| catalogId | string | yes | the numeric catalog id from list_meta_catalogs |
| limit | number | no | max rows (1–100, default 25) |
Create a NEW product catalog on one of the merchant’s Meta business portfolios. A catalog is the input to Advantage+ catalog ads (dynamic product ads) — build it first, then fill it from the merchant’s feed or Shopify, then point a campaign at it. Name the owning portfolio EITHER by its numeric businessId OR by businessName, exactly as list_meta_catalogs prints it (“DMDB Group”) — a name that matc
| Parameter | Type | Required | Notes |
|---|---|---|---|
| businessId | string | no | numeric business-portfolio id that will OWN the catalog (from list_meta_catalogs). Meta will not let a catalog move to another portfolio later. A portfolio NAME is accepted here too and is resolved se |
| businessName | string | no | the owning business portfolio by NAME instead of by id, exactly as list_meta_catalogs prints it (e.g. "DMDB Group"). Unknown or ambiguous names are refused with the real ids rather than guessed. Give |
| name | string | yes | what to call the catalog |
| vertical | string | no | the catalog’s industry (default commerce). PERMANENT — Meta publishes no way to change it after creation. |
| allowDuplicateName | boolean | no | create it even though the portfolio already has a catalog with this name. Off by default: Meta would build an indistinguishable twin without complaining. |
Change a product catalog’s settings — most often its NAME. Also accepts the default and fallback product image URLs (what Meta shows when a product row has no usable image), the dynamic-ad display settings, and the destination / flight / store / partner-integration settings for travel and local-inventory catalogs. A catalog’s VERTICAL and its owning business portfolio are permanent and cannot be c
| Parameter | Type | Required | Notes |
|---|---|---|---|
| catalogId | string | yes | numeric catalog id from list_meta_catalogs |
| name | string | no | new name for the catalog |
| defaultImageUrl | string | no | public http(s) image URL Meta falls back to for a product with no image |
| fallbackImageUrl | string | no | public http(s) image URL used when the default also fails |
| daDisplaySettings | object | no | dynamic-ad display settings object |
| destinationCatalogSettings | object | no | destination-catalog settings object (travel verticals) |
| flightCatalogSettings | object | no | flight-catalog settings object |
| storeCatalogSettings | object | no | store-catalog settings object (local inventory) |
| partnerIntegration | object | no | partner-integration settings object |
| additionalVerticalOption | string | no | additional vertical option |
Read what is INSIDE a product catalog — how many products, which product sets — and get the exact sentence describing what deleting it would destroy. Free and read-only. CALL THIS BEFORE PROPOSING A DELETE: deleting a catalog is permanent and Meta offers no archive and no undo, so naming the cost should not require aiming the destructive call at the target and reading its refusal. It also gives yo
| Parameter | Type | Required | Notes |
|---|---|---|---|
| catalogId | string | yes | numeric catalog id from list_meta_catalogs |
PERMANENTLY delete a product catalog. Meta does not let a catalog be removed from a business portfolio and then restored, and it cannot be transferred to another portfolio — there is no archive and no undo, and its product sets go with it, so any ad set still bound to one of those sets keeps spending and stops having products to show. Confirming is therefore not enough on a catalog that holds anyt
| Parameter | Type | Required | Notes |
|---|---|---|---|
| catalogId | string | yes | numeric catalog id from list_meta_catalogs |
| confirm | boolean | no | true once the user has seen exactly what this destroys and said yes |
| confirmName | string | no | the catalog’s exact name, echoed back — required for any catalog that holds products or sets |
| confirmChildren | number | no | the total number of products + product sets it holds, echoed back |
| allowDeleteWithLiveProductSet | boolean | no | Meta BLOCKS the delete by default when a product set is live. Setting this forces it through, which changes what a campaign that is spending RIGHT NOW shows. Never set it without saying so. |
The people who OPTED IN to marketing messages from a Facebook Page — their subscription tokens, the only address a paid Messenger marketing message can go to (there is no "message everyone"). Also reports whether this Meta connection carries the marketing_messages_messenger permission; a connection made before it was added must be reconnected. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| pageId | string | no | Facebook Page id (from list_meta_pages); defaults to the one shared Page |
| limit | number | no | 1–1000, default 100 |
| customAudienceIds | string | no | comma-separated Messenger custom audience ids to filter by |
The container a paid Messenger marketing message is sent from (Meta act_<AD>/message_campaign). Budgets are in the AD ACCOUNT’s currency (the *Usd parameter names are historical); Meta bills per DELIVERED message against the campaign budget, and creating it sends and charges nothing. A NEW campaign cannot send for about an hour (61 minutes measured 2026-09-14) while Meta populates and prepares it
| Parameter | Type | Required | Notes |
|---|---|---|---|
| adAccountId | string | yes | ad account id (act_… or digits) |
| pageId | string | no | the sending Page; defaults to the one shared Page |
| name | string | yes | campaign name, shown in Meta and in list_messenger_marketing_campaigns |
| dailyBudgetUsd | number | no | daily budget in the ad account's currency, minimum 30; set this or lifetimeBudgetUsd, not both |
| lifetimeBudgetUsd | number | no | total budget in the ad account's currency across the campaign, minimum 30; needs endTime |
| pixelId | string | no | Meta Pixel for offsite conversion attribution |
| startTime | string | no | ISO time, now or within 30 days |
| endTime | string | no | ISO time the campaign stops sending |
| dryRun | boolean | no | true = show the exact request body and create nothing |
The marketing-message campaigns on an ad account: name, id, budgets, schedule. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| adAccountId | string | yes | ad account id (act_ prefix or digits) |
Whether a paid Messenger marketing message was delivered. send_messenger_marketing_message returns a tracking id, which only means Meta ACCEPTED it; this reads what Meta pushed back on the Page webhook for each tracking id: sent, delivered (the event Meta bills on), read, clicked (with count), or failed (with Meta’s reason). "unknown" means NO event has arrived, never that it failed: events arrive
| Parameter | Type | Required | Notes |
|---|---|---|---|
| trackingId | string | no | the marketing_message_tracking_id send_messenger_marketing_message returned |
| trackingIds | array | no | several at once, ≤200 |
| campaignId | string | no | instead of ids: every send Hermoso logged from this campaign |
| pageId | string | no | the sending Page, for the subscription check; defaults to the one shared Page |
Meta’s own estimate of how many marketing messages a budget would deliver from a Page and what it would cost, before anything is created. Free. Meta marks the metric “in development”, so treat it as a range.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| adAccountId | string | yes | ad account id (act_ prefix or digits) |
| pageId | string | no | sending Page; omit to use the shared Page |
| dailyBudgetUsd | number | no | daily budget in the ad account's currency to estimate delivery for |
| lifetimeBudgetUsd | number | no | total budget in the ad account's currency to estimate delivery for |
| lifetimeDays | number | no | number of days the lifetime budget spans, for the estimate |
Send a PAID marketing message on Messenger to opted-in subscribers (Meta act_<AD>/messages). A campaign created in the last ~hour answers 2300012 then 2300041 (Meta still preparing it) with the seconds left; prefer an existing campaign from list_messenger_marketing_campaigns. One message per subscriber per 12 hours — Meta’s rule, enforced before dispatch and by Meta. message.type: text | button (t
| Parameter | Type | Required | Notes |
|---|---|---|---|
| adAccountId | string | yes | ad account id (act_ prefix or digits) |
| campaignId | string | yes | the message campaign id (from create_messenger_marketing_campaign or list_messenger_marketing_campaigns) — OR the campaign NAME as the user said it: a non-numeric value is resolved against the account |
| campaignName | string | no | the campaign by NAME instead of id (case-insensitive, exactly one match wins; none or several is refused naming the campaigns that exist) |
| subscriptionTokens | array | no | subscriber tokens to send to, up to 200 per call; from list_messenger_subscribers |
| customAudienceId | string | no | MESSENGER_SUBSCRIBER_LIST audience of 100+ people, for a bulk send |
| message | object | yes | the message to send: {type, text, title, subtitle, imageUrl, videoId, url, buttons} |
| minConversationGapSeconds | number | no | hold the send if the person chatted with the Page within this many seconds |
| dryRun | boolean | no | true = preview the request body and send nothing |
Send Meta’s opt-in request (the notification_messages template) to someone ALREADY in a Messenger conversation with the Page, inside the 24-hour window. If they accept, Meta posts a messaging_optin webhook with their subscription token and they appear in list_messenger_subscribers. One identical-title request per person per week. Free to send. The PSID comes from the Page inbox.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| pageId | string | no | Page that sends the request; omit when only one Page is connected |
| psid | string | yes | the person’s Page-scoped id |
| title | string | yes | what they are subscribing to, ≤65 characters |
| imageUrl | string | no | optional image shown with the opt-in request |
| timezone | string | no | their timezone, e.g. America/New_York |
| dryRun | boolean | no | true = preview the request and send nothing |
Delete a marketing-message campaign. Irreversible: the first call names it and refuses; confirm:true deletes.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| adAccountId | string | yes | ad account id (act_ prefix or digits) |
| campaignId | string | yes | marketing-message campaign id, from list_messenger_marketing_campaigns |
| confirm | boolean | no | true to delete; without it the call only names the campaign and deletes nothing |
The merchant's real Shopify products — id, title, description, price, images and storefront URL. This is where the productId for publish_to_shopify_product comes from, and it doubles as ground truth about what the brand actually sells (real titles and real photos, not a guess from the website). Newest-updated first. Only works for accounts created by installing Hermoso from the Shopify App Store.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| limit | number | no | how many products (1–100, default 24) |
| cursor | string | no | pageInfo.endCursor from a previous call, to page further |
Attach a finished IMAGE or VIDEO to one of the merchant's Shopify product listings, as product media. Pass productId (from list_shopify_products, the gid://shopify/Product/… form) and a PUBLIC https mediaUrl, which is what every Hermoso render returns. Whether it is a video is read from the file (.mp4 / .mov / .webm, or the link's content type); pass kind to say so outright. An image is fetched by
| Parameter | Type | Required | Notes |
|---|---|---|---|
| productId | string | yes | gid://shopify/Product/… from list_shopify_products |
| mediaUrl | string | no | any public https image or video URL: a Hermoso render works, and so does ANY file of your own brought in with upload_file |
| imageUrl | string | no | older name for mediaUrl; still accepted |
| kind | string | no | only when the link does not say (no file extension and no content type) |
| alt | string | no | alt text for accessibility and SEO; defaults to a generic credit |
The brand's Mailchimp AUDIENCES (lists): subscribed member count, total contacts, unsubscribes, double opt-in, the default from name and email, and when each last sent. An audience id is what create_mailchimp_campaign and sync_leads_to_mailchimp take. Read-only, free. Needs Mailchimp connected (Settings > Connectors > Mailchimp, or connect_connector with an API key).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| limit | number | no | — |
| offset | number | no | — |
The SEGMENTS and tags of one Mailchimp audience with their member counts and type (saved, static/tag, fuzzy). A segment id narrows a campaign to part of the audience. Read-only, free. Needs Mailchimp connected (Settings > Connectors > Mailchimp, or connect_connector with an API key).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| audienceId | string | yes | — |
| limit | number | no | — |
| offset | number | no | — |
The brand's Mailchimp CAMPAIGNS, newest first: title, status (save = draft, schedule, sending, sent, paused), subject, preview text, sender, audience and segment, recipient count, send time. Filter by status or audienceId; or pass campaignId to read ONE with Mailchimp's own send checklist (is it ready, and what is missing). For opens, clicks and revenue use mailchimp_report. Read-only, free. Needs
| Parameter | Type | Required | Notes |
|---|---|---|---|
| campaignId | string | no | — |
| status | string | no | — |
| audienceId | string | no | — |
| limit | number | no | — |
| offset | number | no | — |
The brand's own Mailchimp EMAIL TEMPLATES (name, id, created, edited). A template id is what create_mailchimp_campaign sends. Read-only, free. Needs Mailchimp connected (Settings > Connectors > Mailchimp, or connect_connector with an API key).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| limit | number | no | — |
| offset | number | no | — |
The pictures in the brand's Mailchimp FILE MANAGER: id, name, hosted URL, size and dimensions, newest first. Read-only, free. Needs Mailchimp connected (Settings > Connectors > Mailchimp, or connect_connector with an API key).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| limit | number | no | — |
| offset | number | no | — |
Put a picture into the brand's Mailchimp FILE MANAGER for use in emails: a Hermoso render or Library id (list_library gives L0, L1...), a Hermoso link, or an upload_file URL (for an outside picture, call upload_file first). JPEG, PNG and GIF go up as they are; another format or a file over 5 MB is converted to a JPEG first; a video is refused. Returns Mailchimp's own file row (id and hosted URL).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| image | string | yes | a Library id (L0), or a Hermoso or upload_file URL |
| name | string | no | — |
| folderId | number | no | a File Manager folder id |
Make an EMAIL TEMPLATE in Mailchimp from a creative: image (any reference upload_mailchimp_image takes; it goes into the File Manager first) plus copy YOU write in the brand's voice (read get_brand for voice and colours): headline, body (paragraphs split by a blank line), ctaText + ctaUrl for the button, accent (#RRGGBB button colour). It is laid out as a 600px email with the unsubscribe link and
| Parameter | Type | Required | Notes |
|---|---|---|---|
| name | string | no | — |
| image | string | no | a Library id, or a Hermoso or upload_file URL |
| imageAlt | string | no | — |
| headline | string | no | — |
| body | string | no | — |
| ctaText | string | no | — |
| ctaUrl | string | no | — |
| accent | string | no | #RRGGBB |
| background | string | no | #RRGGBB |
| html | string | no | your own full HTML instead of the layout |
Replace a Mailchimp template's HTML (Mailchimp's update replaces the whole template, so pass the full html), optionally with a new name. The unsubscribe footer is added if the html has none. Free. Needs Mailchimp connected (Settings > Connectors > Mailchimp, or connect_connector with an API key).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| templateId | string | yes | — |
| html | string | yes | — |
| name | string | no | — |
PERMANENTLY delete a Mailchimp template. Without confirm:true nothing is deleted and you get the template it would delete; call again with confirm:true only after the user says yes. Free. Needs Mailchimp connected (Settings > Connectors > Mailchimp, or connect_connector with an API key).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| templateId | string | yes | — |
| confirm | boolean | no | true only after the user approved deleting this template |
Send ONE TEST of a Mailchimp campaign draft to up to 5 addresses the user names (never to the audience), so they can see it in a real inbox. Without confirm:true nothing is sent and you get the preview; call again with confirm:true after they say yes. Free. Needs Mailchimp connected (Settings > Connectors > Mailchimp, or connect_connector with an API key).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| campaignId | string | yes | — |
| recipients | array | yes | up to 5 email addresses |
| confirm | boolean | no | — |
Create a Mailchimp email CAMPAIGN DRAFT: templateId (create_mailchimp_template), subject, previewText, audienceId and optionally segmentId (part of the audience), optional name, fromName and replyTo (default: the audience's own defaults). It is created as a DRAFT and NOTHING IS SENT; the answer is Mailchimp's own row read back with its send checklist. To send or schedule it use send_mailchimp_camp
| Parameter | Type | Required | Notes |
|---|---|---|---|
| templateId | string | yes | — |
| subject | string | yes | — |
| audienceId | string | yes | — |
| previewText | string | no | — |
| segmentId | string | no | — |
| name | string | no | — |
| fromName | string | no | — |
| replyTo | string | no | — |
Edit a Mailchimp campaign that is still a DRAFT: name, subject, previewText, fromName, replyTo, templateId, or its audience / segment. A scheduled one must be unscheduled first (cancel_mailchimp_campaign). Read back with its send checklist. Free. Needs Mailchimp connected (Settings > Connectors > Mailchimp, or connect_connector with an API key).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| campaignId | string | yes | — |
| name | string | no | — |
| subject | string | no | — |
| previewText | string | no | — |
| fromName | string | no | — |
| replyTo | string | no | — |
| templateId | string | no | — |
| audienceId | string | no | — |
| segmentId | string | no | — |
SEND or SCHEDULE a Mailchimp campaign draft. An email cannot be unsent, so this is gated on how many people it reaches: without confirm:true nothing is sent and you get Mailchimp's own recipient count with the subject, sender, audience, timing and Mailchimp's send checklist. Show the user that number; only after they say yes call again with confirm:true AND confirmRecipients set to that count (a m
| Parameter | Type | Required | Notes |
|---|---|---|---|
| campaignId | string | yes | — |
| sendTime | string | no | ISO date-time on the quarter-hour; omit to send now |
| timewarp | boolean | no | — |
| confirmRecipients | number | no | the recipient count the dry run showed the user |
| confirm | boolean | no | true only after the user approved this exact send |
Stop a Mailchimp campaign: action 'unschedule' puts a scheduled one back to a draft; 'cancel' stops one that is sending (emails already delivered stay delivered). Without confirm:true nothing changes and you get what would happen. Free. Needs Mailchimp connected (Settings > Connectors > Mailchimp, or connect_connector with an API key).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| campaignId | string | yes | — |
| action | string | no | — |
| confirm | boolean | no | — |
PERMANENTLY delete a Mailchimp campaign (a draft or a sent one; a scheduled one must be unscheduled first). Without confirm:true nothing is deleted and you get the campaign it would delete; call again with confirm:true only after the user says yes. Free. Needs Mailchimp connected (Settings > Connectors > Mailchimp, or connect_connector with an API key).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| campaignId | string | yes | — |
| confirm | boolean | no | true only after the user approved deleting this campaign |
Mailchimp campaign PERFORMANCE: emails sent, unique opens and open rate (and the rate without machine opens), unique clicks and click rate, unsubscribes, bounces, abuse reports and, where a store is connected to Mailchimp, orders and REVENUE, per campaign sent in the last days (default 30), or one campaign by campaignId. Rates are fractions (0.25 = 25%). Read-only, free. Needs Mailchimp connecte
| Parameter | Type | Required | Notes |
|---|---|---|---|
| campaignId | string | no | — |
| days | number | no | 1-3650, default 30 |
| limit | number | no | — |
| offset | number | no | — |
Put the leads the brand's LEAD FORMS collected (Meta, LinkedIn, TikTok, X Ads, Snapchat, ChatGPT Ads) into a Mailchimp AUDIENCE: each lead with an email is added with their name. By default they are NOT subscribed (a non-marketing 'transactional' contact), because a lead form is not consent Hermoso can vouch for; pass consented:true only when the user confirms the form asked for marketing consent.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| audienceId | string | yes | — |
| consented | boolean | no | — |
| since | string | no | ISO date; default 7 days ago |
| platforms | array | no | — |
| dryRun | boolean | no | — |
The brand's Klaviyo LISTS with how many profiles each holds (profile count), single or double opt-in, and when each was created and updated, newest first. Klaviyo pages lists 10 at a time: pass nextCursor for more, or name for one exact list. A list id is a campaign audience (create_klaviyo_campaign listIds) or a lead-sync target. Read-only, free. Needs Klaviyo connected (Settings > Connectors > K
| Parameter | Type | Required | Notes |
|---|---|---|---|
| name | string | no | an exact list name |
| limit | number | no | — |
| cursor | string | no | nextCursor from a previous call |
The brand's Klaviyo SEGMENTS with how many profiles each holds, whether it is active and still processing, newest first, 10 per page (nextCursor for more). A segment id is a campaign audience (create_klaviyo_campaign segmentIds). Read-only, free. Needs Klaviyo connected (Settings > Connectors > Klaviyo, or connect_connector with a private API key).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| name | string | no | an exact segment name |
| limit | number | no | — |
| cursor | string | no | nextCursor from a previous call |
The brand's Klaviyo CAMPAIGNS, newest first: name, status (draft, scheduled, sending, sent or cancelled, read off its messages), send time, and the list / segment ids it targets. Filter by name (contains), status or archived; or pass campaignId to read ONE in full with its subject line, preview text, sender and template. For opens, clicks and revenue use klaviyo_report. Read-only, free. Needs Klav
| Parameter | Type | Required | Notes |
|---|---|---|---|
| campaignId | string | no | read one campaign in full |
| name | string | no | part of a campaign name |
| status | string | no | — |
| archived | boolean | no | — |
| limit | number | no | — |
| cursor | string | no | nextCursor from a previous call |
The brand's Klaviyo FLOWS (automations such as a welcome series, abandoned cart, post-purchase): name, status (live, manual, draft), trigger type, archived, created and updated, newest first. Filter by status or name. For a flow's opens, clicks and revenue use klaviyo_report with of:'flows'. Read-only, free. Needs Klaviyo connected (Settings > Connectors > Klaviyo, or connect_connector with a priv
| Parameter | Type | Required | Notes |
|---|---|---|---|
| status | string | no | live, manual or draft |
| name | string | no | part of a flow name |
| limit | number | no | — |
| cursor | string | no | nextCursor from a previous call |
The brand's Klaviyo EMAIL TEMPLATES (name, id, editor type, created, updated), newest first, 10 per page. A template id is what create_klaviyo_campaign sends. Read-only, free. Needs Klaviyo connected (Settings > Connectors > Klaviyo, or connect_connector with a private API key).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| name | string | no | part of a template name |
| limit | number | no | — |
| cursor | string | no | nextCursor from a previous call |
The pictures in the brand's Klaviyo IMAGE LIBRARY: id, name, hosted URL, format and size, newest first. Read-only, free. Needs Klaviyo connected (Settings > Connectors > Klaviyo, or connect_connector with a private API key).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| name | string | no | part of an image name |
| limit | number | no | — |
| cursor | string | no | nextCursor from a previous call |
Put a picture into the brand's Klaviyo IMAGE LIBRARY for use in emails: a Hermoso render or Library id (list_library gives L0, L1…), a Hermoso link, an upload_file URL, or a public https image link (Klaviyo fetches an outside link itself). Klaviyo takes JPEG, PNG or GIF up to 5 MB, so another format or a bigger file is converted to a JPEG first; a video is refused. Returns Klaviyo's own image row
| Parameter | Type | Required | Notes |
|---|---|---|---|
| image | string | yes | a Library id (L0), a Hermoso or upload_file URL, or a public https image URL |
| name | string | no | — |
| hidden | boolean | no | keep it out of the visible asset library |
Make an EMAIL TEMPLATE in Klaviyo from a creative: image (any reference upload_klaviyo_image takes; it goes into the image library first) plus copy YOU write in the brand's voice (read get_brand for voice and colours): headline, body (paragraphs split by a blank line), ctaText + ctaUrl for the button, accent (#RRGGBB button colour). It is laid out as a 600px email with the unsubscribe link and sen
| Parameter | Type | Required | Notes |
|---|---|---|---|
| name | string | no | — |
| image | string | no | a Library id, Hermoso or upload_file URL, or a public image URL |
| imageAlt | string | no | — |
| headline | string | no | — |
| body | string | no | — |
| ctaText | string | no | — |
| ctaUrl | string | no | — |
| accent | string | no | #RRGGBB |
| background | string | no | #RRGGBB |
| html | string | no | your own full HTML instead of the layout |
| text | string | no | plain-text version |
Change a Klaviyo template: a new name, html (the unsubscribe footer is added if it has none) or plain text. Read back. Free. Needs Klaviyo connected (Settings > Connectors > Klaviyo, or connect_connector with a private API key).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| templateId | string | yes | — |
| name | string | no | — |
| html | string | no | — |
| text | string | no | — |
PERMANENTLY delete a Klaviyo template. Without confirm:true nothing is deleted and you get the template it would delete; call again with confirm:true only after the user says yes. Free. Needs Klaviyo connected (Settings > Connectors > Klaviyo, or connect_connector with a private API key).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| templateId | string | yes | — |
| confirm | boolean | no | true only after the user approved deleting this template |
Send ONE TEST EMAIL of a Klaviyo template to up to 5 addresses the user names (never to a list), so they can see it in a real inbox. Without confirm:true nothing is sent and you get the preview; call again with confirm:true after they say yes. Klaviyo allows 100 test sends a day. Free. Needs Klaviyo connected (Settings > Connectors > Klaviyo, or connect_connector with a private API key).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| templateId | string | yes | — |
| recipients | array | yes | up to 5 email addresses |
| confirm | boolean | no | — |
Create a Klaviyo email CAMPAIGN DRAFT: templateId (create_klaviyo_template), subject, previewText, the audience as listIds and/or segmentIds (excludeIds leaves people out), optional name, fromEmail / fromLabel (default: the account's default sender) and replyTo. It is created as a DRAFT and NOTHING IS SENT; the answer is Klaviyo's own row read back. To send or schedule it use send_klaviyo_campaign
| Parameter | Type | Required | Notes |
|---|---|---|---|
| templateId | string | yes | — |
| subject | string | yes | — |
| previewText | string | no | — |
| listIds | array | no | — |
| segmentIds | array | no | — |
| excludeIds | array | no | — |
| name | string | no | — |
| fromEmail | string | no | — |
| fromLabel | string | no | — |
| replyTo | string | no | — |
Edit a Klaviyo campaign that is still a DRAFT: name, subject, previewText, fromEmail, fromLabel, replyTo, templateId, or its audience (listIds / segmentIds / excludeIds). A scheduled one must be reverted first (cancel_klaviyo_campaign). Read back. Free. Needs Klaviyo connected (Settings > Connectors > Klaviyo, or connect_connector with a private API key).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| campaignId | string | yes | — |
| name | string | no | — |
| subject | string | no | — |
| previewText | string | no | — |
| fromEmail | string | no | — |
| fromLabel | string | no | — |
| replyTo | string | no | — |
| templateId | string | no | — |
| listIds | array | no | — |
| segmentIds | array | no | — |
| excludeIds | array | no | — |
SEND or SCHEDULE a Klaviyo campaign draft. An email cannot be unsent, so this is gated on how many people it reaches: without confirm:true nothing is sent and you get Klaviyo's own count of recipients with the subject, sender, lists and timing. Show the user that number; only after they say yes call again with confirm:true AND confirmRecipients set to that count (a mismatch, a zero, or a count Kla
| Parameter | Type | Required | Notes |
|---|---|---|---|
| campaignId | string | yes | — |
| sendTime | string | no | ISO date-time in the future; omit to send now |
| strategy | string | no | — |
| confirmRecipients | number | no | the recipient count the dry run showed the user |
| confirm | boolean | no | true only after the user approved this exact send |
Stop a Klaviyo campaign that is scheduled or sending: action 'revert' puts it back to a draft (only before it goes), 'cancel' cancels it for good. Emails already delivered stay delivered. Without confirm:true nothing changes and you get what would happen. Free. Needs Klaviyo connected (Settings > Connectors > Klaviyo, or connect_connector with a private API key).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| campaignId | string | yes | — |
| action | string | no | — |
| confirm | boolean | no | — |
PERMANENTLY delete a Klaviyo campaign (a draft, a sent or a cancelled one; a scheduled one must be reverted first). Without confirm:true nothing is deleted and you get the campaign it would delete; call again with confirm:true only after the user says yes. Free. Needs Klaviyo connected (Settings > Connectors > Klaviyo, or connect_connector with a private API key).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| campaignId | string | yes | — |
| confirm | boolean | no | true only after the user approved deleting this campaign |
Klaviyo PERFORMANCE for campaigns (default) or flows (of:'flows'): recipients, delivered, unique opens and open rate, unique clicks and click rate, conversions and conversion rate, REVENUE (conversion_value) and revenue per recipient, unsubscribes, bounces and spam complaints, per campaign (or per flow message), over timeframe (last_7_days, last_30_days default, last_90_days, this_month, last_12_m
| Parameter | Type | Required | Notes |
|---|---|---|---|
| of | string | no | — |
| timeframe | string | no | — |
| start | string | no | — |
| end | string | no | — |
| statistics | array | no | Klaviyo statistic names, e.g. opens_unique, click_rate, conversion_value |
| campaignIds | array | no | — |
| flowIds | array | no | — |
| channel | string | no | — |
| conversionMetric | string | no | metric name or id; default 'Placed Order' |
| fresh | boolean | no | — |
Put the leads the brand's LEAD FORMS collected (Meta, LinkedIn, TikTok, X Ads, Snapchat, ChatGPT Ads) into a Klaviyo LIST: each lead with an email becomes a Klaviyo profile (name and the form it came from) and joins listId. Nobody is SUBSCRIBED to marketing: a lead form is not consent Hermoso can vouch for, so their consent status is unchanged. Synced leads are remembered per list, so a re-run nev
| Parameter | Type | Required | Notes |
|---|---|---|---|
| listId | string | yes | — |
| since | string | no | ISO date; default 7 days ago |
| platforms | array | no | — |
| dryRun | boolean | no | — |
Support conversations from the brand's OWN Intercom inbox, newest activity first: subject, state, whether the customer is waiting, assignee, customer email, channel, tags and the first message. One Intercom workspace can serve several products, so every filter below combines with the others. view: open (default), unassigned, waiting (customer waiting on a reply), snoozed, closed, recent or all. da
| Parameter | Type | Required | Notes |
|---|---|---|---|
| view | string | no | inbox view; default open |
| days | number | no | 1-365, default 30: conversations updated in the last N days |
| updatedAfter | string | no | date such as 2026-10-01; replaces days |
| updatedBefore | string | no | date |
| createdAfter | string | no | date |
| createdBefore | string | no | date |
| text | string | no | words that appear in the first message |
| string | no | the customer's exact email | |
| customer | string | no | part of the customer's name or email |
| contactId | string | no | an Intercom contact id |
| assigneeId | string | no | the teammate id it is assigned to (list_intercom_teammates) |
| teamId | string | no | the team id it is assigned to (list_intercom_teammates) |
| tagId | string | no | Intercom tag id to filter by, from the Intercom tags list |
| tagName | string | no | a tag name, resolved to its id; use instead of tagId |
| channel | string | no | where the conversation started; conversation is the Messenger |
| priority | boolean | no | true = only priority conversations, false = only non-priority |
| unread | boolean | no | true = only conversations no teammate has read |
| app | string | no | the product the customer wrote from: their contact's current_app attribute, for example Hermoso |
| contactAttributes | object | no | { name: value } custom attributes of the customer's contact record |
| attributes | object | no | { name: value } custom attributes of the conversation |
One Intercom conversation in full: every message in order (customer, teammate, bot, internal note) as plain text, plus its state, assignee and tags. Read it before drafting a reply. Read-only, free. Needs Intercom connected (Settings > Connectors > Intercom, or connect_connector).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| conversationId | string | yes | the conversation id from list_intercom_conversations |
Send a reply to the CUSTOMER on an Intercom conversation, from the brand's inbox as the teammate who owns the connection. This reaches a real customer and cannot be unsent. Read the conversation first (read_intercom_conversation), draft the reply in the brand's voice, show the user the exact words, and send only when they ask: call with confirm:true. Without confirm it sends nothing and returns th
| Parameter | Type | Required | Notes |
|---|---|---|---|
| conversationId | string | yes | Intercom conversation id, from list_intercom_conversations |
| message | string | yes | the exact reply the customer will read, with no [placeholders] |
| confirm | boolean | no | true only after the user approved this exact reply |
Leave an INTERNAL NOTE on an Intercom conversation: only the brand's teammates see it, never the customer (a summary, a suggested reply, a handover). Without confirm:true it adds nothing and returns the preview; call again with confirm:true once the user agrees. Read back from the conversation. Free. Needs Intercom connected (Settings > Connectors > Intercom, or connect_connector).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| conversationId | string | yes | Intercom conversation id, from list_intercom_conversations |
| note | string | yes | the note text teammates will read |
| confirm | boolean | no | true once the user agreed to add this note |
Close, snooze, reopen or assign an Intercom conversation in the brand's shared inbox. action: close | snooze (needs snoozeUntil, a future date-time) | open | assign (assigneeId = a teammate id, or teamId, or assignToMe:true, or unassign:true; ids from list_intercom_teammates). close and assign may carry a message the CUSTOMER sees. Without confirm:true nothing changes and the preview comes back; c
| Parameter | Type | Required | Notes |
|---|---|---|---|
| conversationId | string | yes | Intercom conversation id, from list_intercom_conversations |
| action | string | yes | close, snooze (needs snoozeUntil), open or assign |
| snoozeUntil | string | no | ISO date-time in the future, for snooze |
| assigneeId | string | no | teammate id to assign to, from list_intercom_teammates |
| teamId | string | no | team id to assign to, from list_intercom_teammates |
| assignToMe | boolean | no | true = assign to the connected teammate |
| unassign | boolean | no | true = remove the current assignee |
| message | string | no | optional, close or assign only: the customer sees it |
| confirm | boolean | no | true once the user agreed to this change |
The tags in the brand's Intercom workspace (id and name), for tag_intercom_conversation or to filter list_intercom_conversations. Read-only, free. Needs Intercom connected (Settings > Connectors > Intercom, or connect_connector).
No parameter schema on file.
Add an Intercom tag to a conversation, or remove it (remove:true). Name it by tagId or exact tagName (list_intercom_tags); a tagName the workspace does not have yet is created only with create:true (ask the user first). Internal to the inbox: the customer never sees tags. Read back. Free. Needs Intercom connected (Settings > Connectors > Intercom, or connect_connector).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| conversationId | string | yes | Intercom conversation id to tag |
| tagId | string | no | Intercom tag id from list_intercom_tags; use this or tagName |
| tagName | string | no | exact tag name, as an alternative to tagId |
| remove | boolean | no | true = remove the tag from the conversation; omit or false = add it |
| create | boolean | no | true = create tagName if the workspace has no such tag |
Find people in the brand's Intercom: by exact email, or by part of a name or email (query). Returns id, role (user or lead), name, email, phone, created and last seen. Read-only, free. Needs Intercom connected (Settings > Connectors > Intercom, or connect_connector).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| string | no | exact email | |
| query | string | no | part of a name or email |
| limit | number | no | 1-50, default 10 |
| cursor | string | no | nextCursor from a previous result, to get the next page |
The brand's Intercom teammates and teams with their ids, for assigning a conversation or filtering the list by assignee. Read-only, free. Needs Intercom connected (Settings > Connectors > Intercom, or connect_connector).
No parameter schema on file.
What CUSTOMERS wrote in the brand's Intercom: reads recent conversations in full and returns only the customers' own words (no teammate or bot text; emails, phone numbers and links redacted; no names). Use it for real pains, objections, questions and exact phrases for ad copy, hooks, angles and research, and quote their words rather than paraphrasing. days (default 30), conversations to read (defa
| Parameter | Type | Required | Notes |
|---|---|---|---|
| days | number | no | look-back window in days for conversations |
| conversations | number | no | 1-60, default 25 |
| text | string | no | only read conversations containing this text |
| tagId | string | no | only read conversations with this Intercom tag id (list_intercom_tags) |
| view | string | no | conversations to read: open, closed or all |
Report whether this profile holds the TikTok ACCOUNT-HOLDER authorization — the SECOND, separate consent on the same TikTok app that the TikTok Ads connection uses. TikTok issues two different grants: the ADVERTISER one (campaigns, budgets, reporting — that is tiktok_ads) and this ACCOUNT one, which is what lets Hermoso read and manage the comments on the brand's own TikTok posts and mint Spark-
No parameter schema on file.
Read the comments on a TikTok post the AUTHORIZED ACCOUNT OWNS — this is TikTok's answer to list_meta_comments and list_youtube_comments. It sees BOTH public and hidden comments, and by default returns both: TikTok's status defaults to ALL, so the list mixes comments the owner hid with comments TikTok's own moderation, privacy or spam filters hid, and those are not the same thing (the second kin
| Parameter | Type | Required | Notes |
|---|---|---|---|
| videoId | string | yes | the TikTok post id (item_id) — the last path segment of a tiktok.com/@handle/video/<id> URL |
| commentIds | array | no | filter to specific comment ids; TikTok caps this at 30 |
| includeReplies | boolean | no | attach up to THREE replies per comment — not all of them |
| status | string | no | default ALL, which INCLUDES hidden comments |
| sortField | string | no | likes, replies or create_time |
| sortOrder | string | no | asc or desc |
| cursor | number | no | pagination cursor from the previous reply's cursor field |
| maxCount | number | no | number of comments to return in this page |
All replies to a single comment on a post the authorized TikTok account owns — the complete list, where list_tiktok_comments only ever attaches three. TIKTOK DOES NOT RETURN REPLIES TO A HIDDEN COMMENT AT ALL, so an empty list against a hidden parent means 'cannot read', never 'no replies' — check the parent's status first if the answer looks wrong. NEEDS THE TIKTOK ACCOUNT AUTHORIZATION (see tikt
| Parameter | Type | Required | Notes |
|---|---|---|---|
| videoId | string | yes | TikTok requires the post id alongside the comment id |
| commentId | string | yes | from list_tiktok_comments |
| status | string | no | PUBLIC or ALL (ALL includes hidden replies) |
| sortField | string | no | likes, replies or create_time |
| sortOrder | string | no | asc or desc |
| cursor | number | no | pagination cursor from the previous reply's cursor field |
| maxCount | number | no | number of replies to return in this page |
Write a NEW top-level comment on a TikTok post the authorized account owns. Text (≤1,200 characters, UTF-8) or an image, and TikTok requires at least one of the two. AN IMAGE HERE MUST BE UPLOADED FIRST — a raw URL is refused on a new comment; call upload_tiktok_comment_image and pass back imageUri + imageWidth + imageHeight together (a reply is the one place TikTok accepts a plain URL). TIKTOK SI
| Parameter | Type | Required | Notes |
|---|---|---|---|
| videoId | string | yes | the TikTok post id |
| text | string | no | ≤1,200 characters (UTF-8). Either this or an image is required. |
| imageUri | string | no | from upload_tiktok_comment_image — a raw URL is NOT accepted on a new comment |
| imageWidth | number | no | required with imageUri |
| imageHeight | number | no | required with imageUri |
Reply to an existing comment on a TikTok post the authorized account owns — TikTok's twin of reply_to_meta_comment and reply_to_youtube_comment. Text (≤1,200 characters) or an image; at least one is required. A REPLY IS THE ONE PLACE TIKTOK ACCEPTS A PLAIN IMAGE URL (pass imageUrl); an uploaded imageUri + width + height also works. TikTok silently hides replies it flags as spam, so read it back wi
| Parameter | Type | Required | Notes |
|---|---|---|---|
| videoId | string | yes | the post the comment sits on — TikTok requires it |
| commentId | string | yes | from list_tiktok_comments |
| text | string | no | ≤1,200 characters (UTF-8) |
| imageUrl | string | no | a public image URL — TikTok accepts this on a REPLY only, never on a new comment |
| imageUri | string | no | from upload_tiktok_comment_image |
| imageWidth | number | no | pixel width of the image sent as imageUri (from upload_tiktok_comment_image) |
| imageHeight | number | no | pixel height of the image sent as imageUri |
Moderate one comment on a TikTok post the authorized account owns: LIKE, UNLIKE, HIDE, UNHIDE or DELETE. TWO RULES TIKTOK ENFORCES AND THIS TOOL STATES UP FRONT. (1) YOU CAN ONLY DELETE A COMMENT THIS ACCOUNT WROTE — anyone else's can be hidden but never deleted, so HIDE is the tool for an unwanted comment from a stranger, and it is reversible. DELETE is permanent and confirm-gated. (2) UNHIDE IS
| Parameter | Type | Required | Notes |
|---|---|---|---|
| commentId | string | yes | from list_tiktok_comments |
| action | string | yes | LIKE, UNLIKE, HIDE, UNHIDE or DELETE; DELETE only works on comments this account wrote |
| videoId | string | no | REQUIRED for HIDE and UNHIDE; pass it on the others too and the reply reads the comment back to prove the change landed |
| confirm | boolean | no | REQUIRED true for DELETE, which is permanent — call without it to see exactly what would happen |
Turn a public image URL into the imageUri that comment_on_tiktok_video needs — TikTok will not take a raw URL on a new comment, only on a reply. Returns imageUri, imageWidth and imageHeight, and ALL THREE must be passed back together: TikTok rejects dimensions that do not match what it stored. Limits are TikTok's own — at most 5 MB, JPG/JPEG/PNG/WebP, between 360x360 and 1080x1920 (or 1920x1080).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| imageUrl | string | yes | a public URL to the image — upload_file turns a local file into one |
Turn TikTok's 'Ad authorization' setting ON or OFF for a post the authorized account owns — THIS IS WHERE A SPARK ADS AUTHORIZATION CODE COMES FROM. For somebody ELSE's post the code still comes from that creator, copied out of the TikTok app. IT IS CONFIRM-GATED ON THE WAY ON, and not because it spends: TikTok changes the post's privacy to 'Available for Ads', sends it to their ad review team, sa
| Parameter | Type | Required | Notes |
|---|---|---|---|
| itemId | string | yes | the TikTok post id |
| enabled | boolean | yes | true turns Ad authorization ON (and mints the Spark Ads code); false turns it off |
| authorizationDays | number | no | 7 | 30 | 60 | 180 | 365 — TikTok publishes exactly those five; default 30 |
| confirm | boolean | no | REQUIRED true when enabling — it makes the post publicly promotable and accepts TikTok’s advertising terms on the owner’s behalf |
Read the Spark-Ads authorization status of a post the authorized account owns — whether it is promotable, its authorization CODE, and the window the authorization runs for. A SPARK AD CANNOT OUTLIVE ITS AUTHORIZATION, so check the end time before building a campaign around a post. TIKTOK ERRORS RATHER THAN ANSWERING when a post has no authorization code at all, so a not-found style refusal from he
| Parameter | Type | Required | Notes |
|---|---|---|---|
| itemId | string | yes | the TikTok post id |
Extend how long a post the authorized account owns stays promotable as a Spark Ad — and REGENERATE its code if it was deleted. THE DAYS ARE ADDED, NOT SET: TikTok's own example is that a post with 180 days remaining, extended by 180, ends up at 360 — so passing '365' to a post that already has time left does not mean 'expires in a year'. Must be one of 7, 30, 60, 180, 365. Ad authorization has to
| Parameter | Type | Required | Notes |
|---|---|---|---|
| itemId | string | yes | the TikTok post id |
| authorizationDays | number | no | 7 | 30 | 60 | 180 | 365 — ADDED to whatever is left, not set as an absolute. Default 30. |
Delete the Spark-Ads authorization code for a post the authorized account owns. CONFIRM-GATED, and not because it spends: ads already built on the post keep running, but no NEW ad can be built against it until a fresh code exists, and TikTok REFUSES this outright while any campaign or ad using the post is still ACTIVE — pause those first. Calling it without confirm changes nothing and returns the
| Parameter | Type | Required | Notes |
|---|---|---|---|
| itemId | string | yes | the TikTok post id |
| confirm | boolean | no | REQUIRED true — call without it first to see exactly what would change |
Who is talking about the brand on TikTok: every public post whose CAPTION @-mentions the connected account's handle. This is TikTok's answer to x_mentions and list_threads_mentions, and it is the brand-monitoring read the product had for every other channel and not for TikTok. FOUR THINGS TIKTOK ENFORCES THAT MAKE AN EMPTY RESULT AMBIGUOUS, so read them before reporting silence: the mentioning pos
| Parameter | Type | Required | Notes |
|---|---|---|---|
| days | number | no | look-back window, 1 to 90 (TikTok indexes nothing older). Default 90 |
| regions | array | no | two-letter codes to filter the MENTIONING author's registration country, e.g. ['US','GB']. TikTok publishes 162 of them and answers an unknown one with an empty result, so an unpublished code is refus |
| sortField | string | no | CREATE_TIME, LIKES, COMMENTS or SHARES |
| sortOrder | string | no | ASC or DESC |
| fields | array | no | defaults to every field TikTok publishes; item_id, create_time, video_link, caption, likes, comments, shares, thumbnail_url, views, reach, creator_handle_name |
| cursor | number | no | pagination cursor from the previous reply to fetch the next page |
| maxCount | number | no | 1 to 100 |
Read the full detail of a SINGLE mention: a mentioning post, or a mentioning comment if you pass commentId as well. This exists for the TikTok mentions WEBHOOK: the event carries only ids, and this turns one into the caption, the creator, the engagement and the thumbnail. TIKTOK ONLY GUARANTEES THE DATA FOR 48 HOURS after the event fires, so a queue that retries tomorrow gets nothing. For anything
| Parameter | Type | Required | Notes |
|---|---|---|---|
| itemId | string | yes | the post id. It is video_id in the webhook event content, or itemId from list_tiktok_mentions. REQUIRED even when reading a comment |
| commentId | string | no | pass this to read a mentioning COMMENT rather than a mentioning post |
| fields | array | no | defaults to every field TikTok publishes for that kind |
Comments and replies anywhere on TikTok whose TEXT @-mentions the connected account's handle. This is the conversational half of brand monitoring, where list_tiktok_mentions covers post captions. Same four preconditions as that tool (public post, 90 days, adult author, BUSINESS account), so an empty list is not the same as silence. Capped at the top 1,000 by comment likes, and TIKTOK DELIBERATELY
| Parameter | Type | Required | Notes |
|---|---|---|---|
| days | number | no | 1 to 90. Default 90 |
| regions | array | no | two-letter codes; filters on the commenting author's registration country |
| sortField | string | no | VIDEO_LIKES, COMMENT_CREATE_TIME or COMMENT_LIKES |
| sortOrder | string | no | ASC or DESC |
| fields | array | no | fields to return; omit for every field TikTok publishes |
| cursor | number | no | pagination cursor from the previous reply to fetch the next page |
| maxCount | number | no | 1 to 100; TikTok defaults this one to 10 |
The top 20 KEYWORDS and the top 20 HASHTAGS appearing in the captions of the posts that mention this brand. It is what people say when they talk about it, rather than which posts they said it in. Two TikTok endpoints behind one tool because they take identical parameters and answer the same question at two granularities; kind:'KEYWORDS' or kind:'HASHTAGS' calls only one. Counted across the top 1,0
| Parameter | Type | Required | Notes |
|---|---|---|---|
| kind | string | no | default BOTH, which makes two calls |
| regions | array | no | two-letter codes to narrow which mentioning posts are counted |
The brand hashtags this account curates, in two flavours. kind:'ENABLED' is what TikTok is currently counting (with the date each was turned on, and whether it is old enough to remove). kind:'AVAILABLE' is what TikTok will ACCEPT. A tag qualifies once it has appeared in at least three post captions, either from this account or from a post mentioning its handle. A tag containing the account handle
| Parameter | Type | Required | Notes |
|---|---|---|---|
| kind | string | no | default ENABLED |
| username | string | no | normally resolved from the authorization itself. Pass the @handle (without the @) only if that read is refused |
Enable or disable the hashtags TikTok counts as this brand's. ADD takes a LIST of up to 10 per call (50 enabled per brand in total); REMOVE takes exactly ONE, because TikTok publishes no bulk removal and a list would silently drop all but one. TWO TIMING RULES THAT ARE EASY TO TRIP: a newly enabled hashtag is not counted for 24 HOURS, so list_tiktok_brand_hashtag_posts shows nothing for it until t
| Parameter | Type | Required | Notes |
|---|---|---|---|
| action | string | yes | ADD (a list of up to 10 tags) or REMOVE (exactly one tag) |
| hashtags | array | no | ADD only. Up to 10, with or without the leading # |
| hashtag | string | no | REMOVE only. Exactly one |
| username | string | no | normally resolved from the authorization itself |
Public posts whose captions carry one of the brand hashtags this account has enabled. It is the hashtag half of brand monitoring, where list_tiktok_mentions covers @-mentions. Omit hashtag for the top posts across every enabled tag; pass one to narrow to it. TWO TIKTOK BEHAVIOURS THAT READ AS BUGS IF NOBODY SAYS THEM: the hashtag filter is CASE-SENSITIVE and must exactly match an enabled tag, an
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| hashtag | string | no | one ENABLED tag, spelled exactly as enabled. The match is case-sensitive |
| days | number | no | 1 to 90. Default 90 |
| regions | array | no | two-letter country codes matched against the posting author's registration country, e.g. US |
| sortField | string | no | CREATE_TIME, LIKES, COMMENTS or SHARES |
| sortOrder | string | no | ASC or DESC |
| fields | array | no | fields to return; omit for every field TikTok publishes |
| cursor | number | no | pagination cursor from the previous reply to fetch the next page |
| maxCount | number | no | 1 to 100; TikTok defaults this one to 10 |
The connected TikTok account's OWN analytics: follower demographics broken down by AGE, GENDER, COUNTRY and CITY, the daily series (video views, profile views, likes, comments, shares, reached audience, engaged audience, follower gained/lost/net, and the profile-button clicks a verified Business account collects), and the lifetime counters. This is TikTok's twin of instagram_insights and youtube_c
| Parameter | Type | Required | Notes |
|---|---|---|---|
| startDate | string | no | YYYY-MM-DD (UTC). Default is 7 days ago; TikTok keeps at most 60 days |
| endDate | string | no | YYYY-MM-DD (UTC). Default is yesterday |
| fields | array | no | defaults to everything this authorization can read. Demographics are audience_ages, audience_genders, audience_countries, audience_cities |
What an average TikTok Business account in a given industry looks like: mean likes, comments, shares, video count, follower count, 30-day follower growth, engagement rate and video views. Pair it with tiktok_account_insights to answer 'are we ahead of our category or behind it', which neither number answers alone. These are TikTok's own cross-account averages, not this brand's numbers. businessCat
| Parameter | Type | Required | Notes |
|---|---|---|---|
| businessCategory | string | yes | TikTok business category, e.g. BEAUTY; one of TikTok's published values |
Per-POST analytics for the connected TikTok account: views, reach, likes, comments, shares, saves, average watch time, full-watch rate, the RETENTION CURVE (share still watching at each second — the hook read), traffic sources (For You, Follow, Search, Sound, profile…), new followers, profile views, website clicks, lead submissions and the audience by country, gender and new-vs-returning. Newest p
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| videoIds | array | no | post ids to read; omit for the newest posts |
| maxCount | number | no | 1-20, default 20 |
| cursor | string | no | pagination cursor from the previous reply, to fetch the next page |
| insights | boolean | no | false = counters only (no retention/traffic/audience) |
Which organic TikTok posts are worth running as Spark Ads: TikTok rates the connected account’s posts HIGH / MEDIUM / LOW, optionally for an objective (REACH, TRAFFIC, VIDEO_VIEWS, ENGAGEMENT, APP_PROMOTION, LEAD_GENERATION, WEB_CONVERSIONS, PRODUCT_SALES) and period (PAST_ONE_MONTH / THREE / SIX). Pass ttoAccountId to rate TikTok One creator-marketplace posts instead. Read-only, free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| objective | string | no | campaign objective to rate posts for, e.g. REACH, TRAFFIC or VIDEO_VIEWS; omit for all |
| time | string | no | PAST_ONE_MONTH, PAST_THREE_MONTHS or PAST_SIX_MONTHS |
| videoIds | array | no | rate only these (up to 20) |
| excludeVideoIds | array | no | post ids to leave out of the ratings |
| ttoAccountId | string | no | rate TikTok One creator posts instead of your own |
Hashtag ideas for a TikTok caption: 10 suggestions for a keyword, exact matches first and then by how many posts use them, with each tag’s post count. Read-only, free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| keyword | string | yes | ONE word, no spaces (TikTok refuses a keyword with a space) |
| language | string | no | TikTok language code, default en (e.g. es, fr, de-DE, pt-BR) |
List the LinkedIn COMPANY PAGES the connected account administers — id, name and the role held on each. ALWAYS call this before post_to_linkedin_page when there is more than one Page: publishing to the wrong company Page is a public mistake and Hermoso never chooses for the user. If it comes back empty, the account holds no Page admin role, or LinkedIn has not granted this app the organization sco
No parameter schema on file.
Use this when the user wants to post to a LinkedIn company Page. Publishing costs no credits. Publish a post to one of the user’s LinkedIn COMPANY PAGES — text, plus optionally an image, a video, a 2–20 image CAROUSEL (LinkedIn calls it a MultiImage post; pass the slides in order as imageUrls[]), or a LINK POST with a real preview card (linkUrl). USE linkUrl WHENEVER THE POINT OF THE POST IS A LIN
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no profile, or two, is R |
| hook | string | no | WHAT ANGLE this post is built on, recorded only at publish time. Omit it and this post never votes on which hook works. |
| subject | string | no | WHAT THIS POST IS ABOUT — product, feature, offer or theme (e.g. "winter coat", "free trial"). post_performance's second grouping axis: reuse the exact wording, as with hook. |
| recipe | string | no | the post's FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format |
| ideaId | string | no | short id of the content-plan idea this post came from |
| organizationId | string | no | numeric Page id from list_linkedin_pages |
| text | string | yes | the post text |
| linkUrl | string | no | publish a LINK POST — LinkedIn renders a real preview card for this URL instead of leaving a bare link in the text. Mutually exclusive with imageUrl / videoUrl / imageUrls: LinkedIn’s content field is |
| linkTitle | string | no | the headline ON the preview card. LINKEDIN NEVER SCRAPES THE PAGE — their Posts API disables URL scraping for API partners outright — so if you do not pass this the card renders UNLABELLED. Fetch the |
| linkDescription | string | no | the sub-line on the preview card. Same rule as linkTitle: absent means blank, because LinkedIn will not fetch it. |
| linkThumbnailUrl | string | no | a Hermoso-hosted image used as the card’s picture (uploaded to LinkedIn for you). Without it the card has no image. |
| documentUrl | string | no | a DOCUMENT post — a Hermoso-hosted PDF / PPTX / DOCX (upload_file first) that readers swipe through page by page in the feed; LinkedIn takes up to 100MB and 300 pages. One kind of media per post: not |
| documentTitle | string | no | the title shown on the document (defaults to the file name) |
| mentions | array | no | TAG COMPANIES: write "@Name" in the text where each tag goes and list each company here — its vanity name (the part after linkedin.com/company/), its company page URL, or {organizationId, name}. Hermo |
| imageUrl | string | no | a Hermoso-hosted image URL — a render (list_library), or ANY image of the user’s own passed through upload_file first. An arbitrary external host is refused. |
| videoUrl | string | no | a Hermoso-hosted video URL — a render, or the user’s own footage via upload_file. LinkedIn processes it before publishing, which takes a minute. |
| imageUrls | array | no | CAROUSEL — an ORDERED list of image (and, where the channel allows, video) URLs published as ONE post the viewer swipes through. THIS IS NOT “post several” — it is a single post with several slides, w |
| idempotencyKey | string | no | SAFE RETRIES — any stable string: a repeat of the SAME publish within 24h returns the ORIGINAL post id instead of posting again (an identical publish is also auto-recognised for 10 minutes). On a time |
| allowDuplicate | boolean | no | post it even though an identical post was just made or attempted. Only pass this when the user genuinely wants the same thing posted twice, or when you have LOOKED at the account and confirmed a timed |
| altText | no | accessibility alt text (max 4086 characters, ~120 recommended). A STRING describes every image; an ARRAY describes each slide of a multi-image post separately, in slide order — LinkedIn stores altText |
Read the comments people have left on one of your LinkedIn company Page's posts. Community Management surface, and it needs its OWN connection: LinkedIn grants the comment scopes on a different app from the one that posts, so connecting LinkedIn does NOT connect this — the card is “LinkedIn Community Management” in Settings > Connectors. Pass the post's urn, which is what post_to_linkedin_page ret
| Parameter | Type | Required | Notes |
|---|---|---|---|
| postUrn | string | yes | the post to read — urn:li:share:…, urn:li:ugcPost:… or urn:li:activity:… |
| limit | number | no | how many to fetch (default 50; clamped below LinkedIn’s own 200 timeout bound rather than refused) |
Speak AS your LinkedIn company Page: comment on one of its posts, or reply to somebody else's comment on it. Community Management surface — same separate connection as list_linkedin_comments. organizationId is WHO IS SPEAKING (the Page, from list_linkedin_pages) and postUrn is WHAT IS BEING COMMENTED ON; they are different things and both are required. To reply to a particular comment rather t
| Parameter | Type | Required | Notes |
|---|---|---|---|
| organizationId | string | no | the company Page commenting — OPTIONAL: defaults to the Page shared with this profile on the LinkedIn connector, and is only needed when several are shared |
| postUrn | string | yes | the post being commented on |
| text | string | yes | the comment text |
| parentComment | string | no | to REPLY to an existing comment, its commentUrn — omit for a top-level comment |
Remove a comment from one of your LinkedIn company Page's posts — your own Page's comment, or somebody else's on your Page. Community Management surface, same separate connection. CONFIRM-GATED: call once without confirm to be refused and shown what would go, then again with confirm:true. Deleting is immediate and public. organizationId is required because LinkedIn refuses a Page-level delete
| Parameter | Type | Required | Notes |
|---|---|---|---|
| postUrn | string | yes | the post the comment sits on |
| commentId | string | yes | the comment id from list_linkedin_comments (its id, not the composite commentUrn) |
| organizationId | string | no | the company Page doing the deleting — OPTIONAL: LinkedIn requires an acting organization, but it defaults to the Page shared with this profile |
| confirm | boolean | no | REQUIRED true to actually delete |
Edit or delete a published LinkedIn post — personal profile or company Page. Pass postUrn, the full urn returned when it was published. action:"edit" changes ONLY THE COPY: LinkedIn does not allow the image or video of a published post to be replaced, so a new visual means a NEW post — tell the user that instead of promising a swap. action:"delete" is immediate and public and requires confirm:true
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| postUrn | string | yes | the full LinkedIn post urn returned by publishing |
| action | string | yes | edit changes the copy only; delete removes the post and needs confirm |
| text | string | no | the new copy, for action:"edit" |
| confirm | boolean | no | REQUIRED true to delete |
Follower count of the PERSONAL LinkedIn profile shared with this brand — the lifetime total and, with startDate / endDate, the new followers per day (LinkedIn’s memberFollowersCount). This is the creator / founder growth number; Company Page followers are in linkedin_page_analytics. It needs a personal profile ticked in Manage accounts (list_connector_accounts provider linkedin, then set_connector
| Parameter | Type | Required | Notes |
|---|---|---|---|
| startDate | string | no | YYYY-MM-DD — include for a daily series |
| endDate | string | no | YYYY-MM-DD, exclusive, default today |
ORGANIC performance for one of the brand’s LinkedIn COMPANY PAGES: total followers, followers gained (organic vs paid) across the window, Page views (all / unique / desktop / mobile), and the impressions, unique impressions, clicks, likes, comments, shares and engagement rate of the Page’s posts. This is what answers “is our LinkedIn actually working” and “did that post land”. LinkedIn excludes sp
| Parameter | Type | Required | Notes |
|---|---|---|---|
| organizationId | string | no | numeric Page id from list_linkedin_pages — omit only when exactly one Page is shared with this profile |
| startDate | string | no | YYYY-MM-DD, default 28 days ago (LinkedIn keeps 12 months) |
| endDate | string | no | YYYY-MM-DD, default today |
| postUrns | array | no | urn:li:share:… / urn:li:ugcPost:… — switches to per-post lifetime numbers instead of the Page total |
| demographics | boolean | no | also return WHO follows the Page and who visits it (all-time, LinkedIn’s lifetime statistics): the top seniorities, industries, job functions, countries, regions and company sizes, each named — the co |
The LEAD GEN FORMS a LinkedIn company Page or ad account owns — id, name, state, version and the fields each one asks for (firstName, email, company …). If it answers that the connection must be reconnected, say exactly that: the lead-sync permission is granted at authorise time. Read-only, free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| pageId | string | no | the company Page that owns the form — from list_linkedin_pages; omit when one Page is shared |
| adAccountId | string | no | read forms owned by an AD ACCOUNT instead of a Page |
The LEADS a LinkedIn lead gen form collected — every response with its answers keyed by field (firstName, lastName, email, company, …), the campaign and creative that produced it, the consents ticked, and whether it was a test lead. Newest first. Filter by formId, a since/until window (ISO date or epoch ms — LinkedIn takes epoch), or testLeadsOnly. THIS IS PERSONAL DATA: show it to the user, hand
| Parameter | Type | Required | Notes |
|---|---|---|---|
| pageId | string | no | the company Page that owns the form — from list_linkedin_pages; omit when one Page is shared |
| adAccountId | string | no | read forms owned by an AD ACCOUNT instead of a Page |
| formId | string | no | only this form (from list_linkedin_lead_forms) |
| formVersion | number | no | default 1 |
| since | string | no | ISO date or epoch milliseconds |
| until | string | no | end of the time window, ISO date or epoch milliseconds; omit for now |
| testLeadsOnly | boolean | no | true returns ONLY test submissions |
| leadType | string | no | defaults by owner: SPONSORED for an ad account, COMPANY (organic Page form) for a Page; EVENT for event forms. LinkedIn refuses SPONSORED on a Page owner |
| limit | number | no | per page, max 100 |
| start | number | no | offset for the next page |
One LinkedIn lead by id (from list_linkedin_leads), with every answer named by field. Personal data — show, never republish. Read-only, free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| leadId | string | yes | lead id from list_linkedin_leads |
| pageId | string | no | the company Page that owns the form — from list_linkedin_pages; omit when one Page is shared |
| adAccountId | string | no | read forms owned by an AD ACCOUNT instead of a Page |
Have LinkedIn push every new lead to Hermoso the moment it is submitted, and optionally relay each event on to the user’s own CRM. THE WEBHOOK LINKEDIN VALIDATES IS ALWAYS HERMOSO’S OWN: LinkedIn challenges it with our app secret (and re-challenges every ~2 hours), which no CRM, Zapier or Make endpoint can answer — so never promise a customer URL as the LinkedIn webhook. Pass forwardTo (public HTT
| Parameter | Type | Required | Notes |
|---|---|---|---|
| forwardTo | string | no | optional public HTTPS URL Hermoso relays each lead event to (a CRM, Zapier, Make) |
| pageId | string | no | the company Page that owns the form — from list_linkedin_pages; omit when one Page is shared |
| adAccountId | string | no | read forms owned by an AD ACCOUNT instead of a Page |
| leadType | string | no | defaults by owner: SPONSORED for an ad account, COMPANY for a Page |
The lead events LinkedIn has PUSHED to Hermoso for this brand (new lead / deleted lead, with the form and the lead id), newest first. Empty means none have arrived, not that none exist — list_linkedin_leads reads every lead regardless, and subscribe_linkedin_leads is what starts delivery. Read a lead’s answers with get_linkedin_lead. Read-only, free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| limit | number | no | events to return, newest first; default 50, max 200 |
The lead notification webhooks registered on a LinkedIn Page or ad account, with the id delete_linkedin_lead_subscription takes. Read-only, free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| pageId | string | no | the company Page that owns the form — from list_linkedin_pages; omit when one Page is shared |
| adAccountId | string | no | read forms owned by an AD ACCOUNT instead of a Page |
| leadType | string | no | SPONSORED for an ad account; COMPANY, EVENT or ORGANIZATION_PRODUCT for a Page; defaults by owner |
Remove a lead notification webhook (subscriptionId from list_linkedin_lead_subscriptions). Leads themselves are unaffected and stay readable; only the real-time delivery stops. Read back from LinkedIn. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| subscriptionId | string | yes | webhook id from list_linkedin_lead_subscriptions |
| pageId | string | no | the company Page that owns the form — from list_linkedin_pages; omit when one Page is shared |
| adAccountId | string | no | read forms owned by an AD ACCOUNT instead of a Page |
Edit the text of, or delete, a published post. target:"facebook" -> edit the message (action:"edit", message:…) OR delete (action:"delete"); target:"threads" -> delete only (Threads has no edit API); target:"instagram" -> DELETE ONLY — Meta lets you change nothing on a published Instagram post except whether comments are enabled, so a caption cannot be fixed; deleting covers ordinary posts, Storie
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| postId | string | yes | the post id returned by post_to_meta — for Instagram, the media id from list_instagram_media |
| action | string | yes | edit the text (FB only), delete the post, or cover: replace the cover of a published Facebook video or Reel (postId = its video id) |
| target | string | no | default facebook |
| coverAtMs | number | no | action:"cover" — the new cover frame, in milliseconds from the start (Facebook only; Instagram, LinkedIn and Pinterest cannot change a published cover) |
| coverImageUrl | string | no | action:"cover" — a Hermoso-hosted picture as the new cover, instead of a frame (a render, or any file through upload_file) |
| videoUrl | string | no | action:"cover" with coverAtMs — the Hermoso file the video was published from, to cut the frame from (else Facebook’s own copy is used) |
| message | string | no | the new post text (action:"edit" on facebook) |
| pageId | string | no | which Page to use — needed when the post id has no page prefix, or when the profile has several Pages and you are deleting an Instagram post |
| confirm | boolean | no | REQUIRED true to delete (permanent) |
| confirmName | string | no | Instagram only: the post’s exact caption line, exactly as the unconfirmed call reported it — required once the post has any likes or comments |
| confirmChildren | number | no | Instagram only: how many carousel cards the delete also destroys, as the unconfirmed call reported |
Save a Hermoso render — or ANY file — into the user’s connected Google Drive. Pass a Hermoso render URL as url (or urls[] for several); for a local/external file, call upload_file first and pass the url it returns. Optional folder (created if new) + name. Returns the Drive file(s) with a webViewLink. Needs Google Drive connected (Settings > Connectors > Google Drive — one connection covers Drive,
| Parameter | Type | Required | Notes |
|---|---|---|---|
| url | string | no | a single Hermoso render URL to save |
| urls | array | no | several render URLs (up to 20) to save in one call |
| folder | string | no | Drive folder name to save into (created if new) |
| name | string | no | file name (single save) |
Pull the files in a Google Drive or OneDrive FOLDER into this profile's Library, so they can be used like anything rendered here — published, scheduled, cloned, used as a product photo or a reference. Hermoso downloads each file with the user's own connected account (a Drive/OneDrive file is not public) and stores a durable Hermoso url for each. Give folderId from list_drive_files / list_onedriv
| Parameter | Type | Required | Notes |
|---|---|---|---|
| provider | string | yes | which cloud — drive is Google Drive, onedrive is Microsoft OneDrive |
| folderId | string | no | the folder to import, from list_drive_files / list_onedrive_files (onlyFolders:true). Omit for the root of the drive. |
| limit | number | no | how many files to bring across in this call (default 10, max 25). Anything over the limit is listed as skipped so you know what is left. |
List the Google Drive files & folders Hermoso can reach: the ones it created plus any the user picked with the Google file picker in the app (drive.file, never their whole Drive). This is how you find a picked file's id. Filter by query (name contains …), folderId, or onlyFolders:true; paginate with pageToken. Read-only. A FOLDER: picking a folder hands over the folder alone, not its files, so a f
| Parameter | Type | Required | Notes |
|---|---|---|---|
| query | string | no | only files whose name contains this |
| folderId | string | no | list the contents of this folder id |
| recursive | boolean | no | with folderId: the whole tree |
| onlyFolders | boolean | no | list folders only |
| pageSize | number | no | rows per page (1–200, default 50) |
| pageToken | string | no | cursor from a previous call |
| includeTrashed | boolean | no | include trashed files (default false) |
Fetch one Drive file’s metadata — name, type, size, modified time, a webViewLink to open it and a webContentLink to download it. Pass fileId (from list_drive_files). Read-only.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| fileId | string | yes | the Drive file id (from list_drive_files) |
Update a Drive file: rename (name), move it into a folder (moveToFolderId, optionally removeFromFolderId to move OUT of the old one), or trash / untrash it (trash:true|false). Pass fileId (from list_drive_files). To delete permanently, use delete_drive_file.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| fileId | string | yes | the Drive file id |
| name | string | no | new name |
| moveToFolderId | string | no | folder id to move the file into (from create_drive_folder / list_drive_files) |
| removeFromFolderId | string | no | the old parent folder id to remove (when moving) |
| trash | boolean | no | true -> move to Trash; false -> restore from Trash |
Delete a Drive file. By default it goes to Trash (recoverable); pass permanent:true to delete it forever. Pass fileId (from list_drive_files) + confirm:true. Irreversible when permanent — confirm with the user first.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| fileId | string | yes | the Drive file id |
| permanent | boolean | no | true = delete forever; default trashes (recoverable) |
| confirm | boolean | no | REQUIRED true |
Create a folder in the user’s Google Drive (optionally nested under parentId) to organize saved files. Returns the folder id + webViewLink. Use that ID as update_drive_file’s moveToFolderId or as parentId for a nested folder. NOTE: save_to_drive’s folder is a NAME, not this id — it find-or-creates a folder by that name, so pass the folder NAME there (or omit and just save, then move with update_
| Parameter | Type | Required | Notes |
|---|---|---|---|
| name | string | yes | folder name |
| parentId | string | no | parent folder id for a nested folder (default: Drive root) |
Create a new Google Spreadsheet in the user’s Drive and optionally fill it with rows — e.g. export a swipefile, ad list, or performance report. Pass rows as an array of row arrays (first row = headers). Returns the spreadsheet id + URL. Needs Google Drive connected (Settings > Connectors > Google Drive — one connection covers Drive, Sheets and Docs).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| title | string | no | spreadsheet title |
| rows | array | no | rows to write — array of row arrays; first row = headers |
Append rows to a Google Sheet Hermoso can reach — one it created (pass the spreadsheetId from create_sheet) or one the user handed over with the Google file picker in the app (find its id with list_drive_files). rows = array of row arrays.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| spreadsheetId | string | yes | the spreadsheet id from create_sheet |
| rows | array | yes | rows to append — array of row arrays |
| range | string | no | range to append at (default A1 / first sheet) |
Read cells from a Google Sheet Hermoso can reach — one it created, or one the user handed over with the Google file picker in the app (that is how an EXISTING spreadsheet becomes readable; find its id with list_drive_files). Pass the spreadsheetId (from create_sheet) OR paste a Google Sheets URL as sheetUrl. If Google answers that the file was not found, the user has not picked it yet — ask them t
| Parameter | Type | Required | Notes |
|---|---|---|---|
| spreadsheetId | string | no | the spreadsheet id (from create_sheet) |
| sheetUrl | string | no | a Google Sheets URL to read — the spreadsheet id is extracted from it |
| range | string | no | A1 range, e.g. "A1:D50" (default A1:Z1000) |
Turn a SWIPEFILE COLLECTION into a real Google Slides deck — one slide per saved ad, carrying the creative, the brand, the ad copy, the run dates with the run length, and the platform. This is the thing a marketer actually presents to a client or a team. Returns the presentation id + URL. Creates a NEW deck every time: under the drive.file scope Hermoso can only touch files it created, so it canno
| Parameter | Type | Required | Notes |
|---|---|---|---|
| collection | string | no | the swipefile collection to export, by name or id (default: the first collection) |
| title | string | no | deck title (default: the collection name) |
| limit | number | no | max ads to include, 1-60 (default 30) |
Find the Notion pages and databases the user shared with Hermoso, by title, newest edited first. Gives the page id that save_to_notion, append_to_notion_page and read_notion_page take, or the database id to save rows into. Read-only, free. Needs Notion connected (Settings > Connectors > Notion).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| query | string | no | words in the title |
| type | string | no | limit results to pages or to databases; omit for both |
| limit | number | no | 1-100, default 25 |
| cursor | string | no | from the previous page |
Save to the user's Notion as a NEW page: a research report, competitor teardown or campaign report as content (markdown: headings, lists, links, and an image or video URL on its own line becomes the media), a swipefile collection (swipefileCollection: name or id; each ad with its creative, copy, run dates and link), the content calendar (includeSchedule:true), or a mix. Put it under parentPageId
| Parameter | Type | Required | Notes |
|---|---|---|---|
| title | string | no | title of the new Notion page |
| content | string | no | markdown body |
| swipefileCollection | string | no | swipefile collection name or id |
| includeSchedule | boolean | no | add the scheduled posts |
| parentPageId | string | no | page id or link to save under |
| databaseId | string | no | database id or link: saves as a new row |
| dataSourceId | string | no | data source id to save into as a new row; pass only one of parentPageId, databaseId, dataSourceId |
| icon | string | no | one emoji |
Add to the END of an existing Notion page (pageId or its link, from list_notion_pages): markdown content, a swipefileCollection, or includeSchedule:true. Nothing already on the page changes. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| pageId | string | yes | page id or link |
| content | string | no | markdown to add |
| swipefileCollection | string | no | swipefile collection name or id to append, with each ad's creative, copy, dates and link |
| includeSchedule | boolean | no | true appends the scheduled posts too |
Read a Notion page the user shared with Hermoso, as markdown (pageId or a Notion link). A database id answers with its data sources instead. Read-only, free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| pageId | string | yes | page id or Notion link |
Export the posting schedule to a Notion database, one row per post: date, channels, caption, media link and status. Creates a new database (under parentPageId, or private in the workspace) or writes into an existing one (databaseId), matching its columns by name. historyDays adds posts already published. Up to 100 rows per run; the answer says if more were left. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| parentPageId | string | no | Notion page to create the new database under; omit for a private page in the workspace |
| databaseId | string | no | an existing database id or link |
| dataSourceId | string | no | data source id of an existing database to write rows into, matched to columns by name |
| title | string | no | name for a new database |
| historyDays | number | no | 0-90 days of published posts to include |
Read the ROWS of a Notion database the user shared with Hermoso — e.g. the team's content calendar: each row's title, id, link and every property as a plain value (status, date, channels, caption, links), plus the database's columns with their types and options. Filter with match {"Status": "Scheduled"} (typed from the database's own columns) or a raw Notion filter object; sort with sortBy (a colu
| Parameter | Type | Required | Notes |
|---|---|---|---|
| databaseId | string | no | database id or link (list_notion_pages type:"database") |
| dataSourceId | string | no | data source id from an earlier read, as an alternative to databaseId |
| match | object | no | {"Column": value} — equality for status/select/checkbox/number/date, contains for text/multi-select |
| filter | object | no | a raw Notion filter object |
| sortBy | string | no | column name to sort by, or created_time or last_edited_time |
| direction | string | no | ascending (default) or descending |
| limit | number | no | 1-100, default 50 |
| cursor | string | no | nextCursor from the previous page of rows |
| archived | boolean | no | read archived rows instead |
Change properties on ONE row of a Notion database (pageId from query_notion_database) — e.g. mark a calendar row Status "Posted" and put the live post link in its URL column. properties is {"Column": value} by column name: text, number, checkbox, select (an existing option or a new one), status (must be an existing option), multi-select (array; replaces the whole value), date (ISO, or {start, end}
| Parameter | Type | Required | Notes |
|---|---|---|---|
| pageId | string | yes | the row id or link |
| properties | object | no | {"Column": value} |
| trash | boolean | no | true moves the row to Notion trash (needs confirm); false restores it |
| confirm | boolean | no | true confirms moving the row to trash; not needed for other changes |
Create a new Google Doc in the user’s Drive with a title + optional body text — e.g. export ad copy, a creative brief, or a report. Returns the document id + URL. Needs Google Drive connected (Settings > Connectors > Google Drive — one connection covers Drive, Sheets and Docs).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| title | string | no | document title |
| text | string | no | body text to insert |
Append text to the end of a Google Doc Hermoso can reach — one it created (pass the documentId from create_doc) or one the user handed over with the Google file picker in the app (find its id with list_drive_files). Optional dropdown:{title, options:[2-50], selected} appends a Google Docs dropdown chip after the text — e.g. text "Status: " + dropdown {title:"Status", options:["Draft","In review"
| Parameter | Type | Required | Notes |
|---|---|---|---|
| documentId | string | yes | the document id from create_doc |
| text | string | no | text to append at the end of the doc (optional when a dropdown is passed) |
| dropdown | object | no | a dropdown chip to append after the text |
Read the text of a Google Doc Hermoso can reach — one it created, or one the user handed over with the Google file picker in the app (that is how an EXISTING doc becomes readable; find its id with list_drive_files). Pass documentId (from create_doc) OR paste a Google Docs URL as docUrl. Under the drive.file scope it reaches nothing else in the user’s Drive; if Google answers that the file was not
| Parameter | Type | Required | Notes |
|---|---|---|---|
| documentId | string | no | the document id (from create_doc) |
| docUrl | string | no | a Google Docs URL to read — the document id is extracted from it |
The tabs in a Google Spreadsheet, each with its name, numeric sheetId, row/column count and position. Call this BEFORE naming a tab in update_sheet / clear_sheet_range / manage_sheet_tabs / format_sheet, and before proposing to delete one — it is how you learn what the file actually contains instead of guessing at a name. Read-only, free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| spreadsheetId | string | no | the spreadsheet id (from create_sheet, or list_drive_files for one the user picked) |
| sheetUrl | string | no | a Google Sheets URL — the id is extracted from it |
CORRECT cells in a Google Sheet — write values to an exact range, overwriting whatever is there. This is the fix append_to_sheet cannot make: appending only ever adds rows at the bottom, so without this a wrong number stays wrong forever and the only "correction" is a second row contradicting the first. Pass range (e.g. "B2:C5", or "Q3 Report!B2" to name a tab — list_sheet_tabs gives the names)
| Parameter | Type | Required | Notes |
|---|---|---|---|
| spreadsheetId | string | no | Google spreadsheet id; use this or sheetUrl |
| sheetUrl | string | no | Google Sheets link, instead of spreadsheetId |
| range | string | no | A1 range or anchor cell, e.g. "B2:C5", "B2", or "Q3 Report!B2" (default A1) |
| values | array | no | array of row arrays to write |
| updates | array | no | write SEVERAL disjoint ranges in one call, instead of range+values |
| valueInputOption | string | no | USER_ENTERED (default) parses formulas, dates and numbers the way typing them would; RAW stores every value as literal text |
| confirm | boolean | no | required only when the target range already holds values |
Empty a range of cells in a Google Sheet, leaving the rows themselves in place. DESTRUCTIVE: call it WITHOUT confirm first and nothing is cleared — you get back the real number of filled cells in that exact range. Show the user that number, get an unambiguous yes, then call again with confirm:true AND confirmCells set to it. The echo is not ceremony: it is what catches naming A1:Z1000 when you mea
| Parameter | Type | Required | Notes |
|---|---|---|---|
| spreadsheetId | string | no | Google spreadsheet id; use this or sheetUrl |
| sheetUrl | string | no | Google Sheets link, instead of spreadsheetId |
| range | string | yes | the range to clear, e.g. "A2:D50" or "Sheet1!A2:D50" |
| confirm | boolean | no | true clears the range; call without it first to get the filled-cell count |
| confirmCells | number | no | echo back the filled-cell count the unconfirmed call reported |
Add, rename or delete a tab in a Google Spreadsheet. action:"add" + title · action:"rename" + tab + newTitle · action:"delete" + tab. Name the tab by its TITLE or its numeric sheetId (list_sheet_tabs gives both); an unknown tab is refused with the real list rather than a Google error nobody can map back. DELETING a tab destroys everything on it: call it without confirm first to get the filled-cell
| Parameter | Type | Required | Notes |
|---|---|---|---|
| spreadsheetId | string | no | Google spreadsheet id; use this or sheetUrl |
| sheetUrl | string | no | Google Sheets link, instead of spreadsheetId |
| action | string | yes | add, rename or delete a tab |
| tab | string | no | which tab — its title or numeric sheetId (rename / delete) |
| title | string | no | the name for the new tab (action:"add") |
| newTitle | string | no | what to rename the tab to (action:"rename") |
| confirm | boolean | no | true deletes a tab that holds data; needs confirmCells |
| confirmCells | number | no | the filled-cell count an unconfirmed delete reported |
Make an exported sheet readable: bold the header row, FREEZE it so it stays visible while scrolling, and auto-size the columns so nothing is cut off. Worth calling right after create_sheet — a raw export with unsized columns and a header that scrolls away is the difference between a spreadsheet someone reads and one they close. Changes no cell VALUE, so it is never gated. The defaults do all three
| Parameter | Type | Required | Notes |
|---|---|---|---|
| spreadsheetId | string | no | Google spreadsheet id; use this or sheetUrl |
| sheetUrl | string | no | Google Sheets link, instead of spreadsheetId |
| tab | string | no | tab title or numeric sheetId (default: the first tab) |
| boldHeader | boolean | no | bold the header row; default true, false skips it |
| freezeRows | number | no | how many top rows to freeze (default 1, 0 = none) |
| autoResize | boolean | no | auto-size columns to fit their content; default true, false skips it |
EDIT a Google Doc — the correction append_to_doc cannot make. Two shapes: replacements:[{find, replace}] rewrites specific text wherever it appears (call read_doc first and match the text EXACTLY; matchCase:false ignores case), or rewrite:"…" replaces the ENTIRE body (rewrite:"" empties it), or dropdowns:[{title, value}] sets a dropdown chip (e.g. Status -> Approved — read_doc lists every ch
| Parameter | Type | Required | Notes |
|---|---|---|---|
| documentId | string | no | the document id (from create_doc, or list_drive_files for one the user picked) |
| docUrl | string | no | a Google Docs URL — the id is extracted from it |
| replacements | array | no | find/replace pairs, applied in order |
| rewrite | string | no | replace the WHOLE body with this text ("" empties the doc) |
| confirm | boolean | no | true runs a whole-body rewrite after the character count is shown |
| confirmCells | number | no | echo back the character count the unconfirmed call reported (rewrite only) |
| dropdowns | array | no | dropdown chips to set, or dropdowns to change |
Save a Hermoso render — or ANY file — into the user’s connected Microsoft OneDrive. Pass a Hermoso render URL as url (or urls[] for several); for a local/external file, call upload_file first and pass the url it returns. Optional folder (created if new) + name. Returns the OneDrive file(s) with a webViewLink. Needs OneDrive connected (Settings > Connectors > OneDrive).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| url | string | no | a single Hermoso render URL to save |
| urls | array | no | several render URLs (up to 20) to save in one call |
| folder | string | no | OneDrive folder name to save into (created if new) |
| name | string | no | file name (single save) |
List files & folders in the user’s OneDrive — the root by default, a folder’s contents (folderId), or a name search (query). onlyFolders:true lists folders only. Paginate with pageToken (the cursor from a previous call). Read-only.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| query | string | no | search — only items whose name matches this |
| folderId | string | no | list the contents of this folder id |
| onlyFolders | boolean | no | list folders only |
| pageSize | number | no | rows per page (1–200, default 50) |
| pageToken | string | no | cursor from a previous call |
Fetch one OneDrive item’s metadata — name, type, size, modified time, a webViewLink to open it and a webContentLink to download it. Pass fileId (from list_onedrive_files). Read-only.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| fileId | string | yes | the OneDrive item id (from list_onedrive_files) |
Update a OneDrive item: rename (name) and/or move it into a folder (moveToFolderId). Pass fileId (from list_onedrive_files). To remove an item, use delete_onedrive_file.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| fileId | string | yes | the OneDrive item id |
| name | string | no | new name |
| moveToFolderId | string | no | folder id to move the item into (from create_onedrive_folder / list_onedrive_files) |
Delete a OneDrive item — it moves to the OneDrive recycle bin (recoverable there). Pass fileId (from list_onedrive_files) + confirm:true. Confirm the exact file with the user first.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| fileId | string | yes | the OneDrive item id |
| confirm | boolean | no | REQUIRED true |
Create a SHARING LINK to a OneDrive file or folder (fileId from list_onedrive_files): type view (read-only, default), edit, or embed (an <iframe> link, personal OneDrive only); scope anonymous (anyone with the link, default), organization (work or school OneDrive only) or users (only people you invite); optional expiresAt (date or ISO) and password (on a personal OneDrive both need a Microsoft 365
| Parameter | Type | Required | Notes |
|---|---|---|---|
| fileId | string | yes | OneDrive file or folder id from list_onedrive_files |
| type | string | no | view (default), edit, or embed (personal OneDrive only) |
| scope | string | no | anonymous (default, anyone with the link), organization (work or school), or users (invited people only) |
| expiresAt | string | no | e.g. 2026-11-30 or 2026-11-30T17:00:00Z |
| password | string | no | personal OneDrive with Microsoft 365 |
| confirm | boolean | no | required for an anonymous edit link |
action list (default): every sharing link and person with access to a OneDrive file or folder: link URL, view or edit, scope, expiry, password, whether it is inherited from a parent folder. action remove: revoke one (permissionId from the list); anyone relying on it loses access at once and the link cannot be restored, so WITHOUT confirm:true nothing is removed and you get what would go. An inheri
| Parameter | Type | Required | Notes |
|---|---|---|---|
| fileId | string | yes | OneDrive file or folder id |
| action | string | no | list (default) or remove |
| permissionId | string | no | for remove |
| confirm | boolean | no | required true for remove |
Turn a file already in the user’s OneDrive into a PDF or a JPG — Microsoft does the conversion on its own servers, so nothing is re-encoded here and nothing is lost in a screenshot. It reads about 130 source formats, which is the point: PowerPoint and Word decks, Excel, Photoshop PSD, Illustrator AI, Sketch, 3D (fbx/glb/obj), video (mp4/mov/webm), HEIC from an iPhone, and the raw camera formats (C
| Parameter | Type | Required | Notes |
|---|---|---|---|
| fileId | string | yes | the OneDrive item id, from list_onedrive_files |
| format | string | no | default pdf |
| width | number | no | REQUIRED for jpg — output width in pixels |
| height | number | no | REQUIRED for jpg — output height in pixels |
Create a folder in the user’s OneDrive (optionally nested under parentId) to organize saved files. Returns the folder id + webViewLink. Use that id as update_onedrive_file’s moveToFolderId or as parentId for a nested folder. NOTE: save_to_onedrive’s folder is a NAME (find-or-created), not this id.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| name | string | yes | folder name |
| parentId | string | no | parent folder id for a nested folder (default: OneDrive root) |
Poll a render job by id. Returns status (queued|running|done|error), progress, and on done the served media URL. Renders take 1–3 minutes: keep calling this until done/error without asking the user — several calls is normal, not a stall. An id that does not exist on this account answers status "not_found" — that is FINAL: stop polling it, and do not re-fire the render (that double-charges).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| id | string | yes | The job id: starts with job_, from a render tool's jobId or from list_jobs |
Use this when the user wants a week of social posts planned, or answers yes or no to planning their week. It drafts one batch of 5 posts for the active brand: a new image, a caption and a suggested day and time for each. Without confirm it returns the price and whether the batch fits the account’s posting allowance, and spends nothing. confirm:true starts the batch and bills credits; read the post
| Parameter | Type | Required | Notes |
|---|---|---|---|
| confirm | boolean | no | true only after the user agreed to the price |
| decline | boolean | no | true when the user said no |
| timezone | string | no | the user’s IANA timezone when known, e.g. America/New_York; the suggested times use it |
List the bundled Hermoso SKILLS — multi-step workflow instructions (SKILL.md) that orchestrate the other tools (research an ad space, plan+render a finished ad, product photoshoot, raw generation) — plus the in-app strategy skills and creative recipes. Call get_skill to load a bundle. Read-only, free.
No parameter schema on file.
Load a bundled skill’s full SKILL.md workflow instructions by name (from list_skills), or a built-in in-app skill by its id (e.g. youtube_script, retention_skeleton). Follow the loaded instructions to run that workflow with the other tools. Read-only, free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| name | string | yes | bundle name from list_skills, e.g. hermoso-generate |
Save a reusable custom SKILL — a named creative directive/playbook applied to future ads (a hook formula, a UGC recipe, a compliance rule, a named specialist persona like “our founder-story style” or “short-form ad strategist”). Distill an imperative, self-contained directive. Merges into the workspace Skills library (list_skills shows built-ins + your custom skills).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | profile id/name from list_brands, this call only |
| name | string | yes | short skill name, e.g. “Founder-story hook” |
| directive | string | yes | the full instruction the skill applies when used (1–6 sentences, imperative) |
Delete one of the workspace’s CUSTOM skills by id (from list_skills). Built-in skills/recipes can’t be deleted. Minor + re-creatable, so no confirm needed.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | profile id/name from list_brands, this call only |
| id | string | yes | the custom skill id (from list_skills) |
List the durable facts & preferences saved in this workspace’s Memory (what the studio remembers about the brand, audience, taste, and do/don’t rules) — the same Memory the web app shows. These shape every future ad. Read-only, free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| category | string | no | filter to one bucket (Brand/Audience/Taste/Do/Don’t/Preference) |
| limit | number | no | max items (default 50, max 200) |
Save a durable fact or PREFERENCE about the brand, audience, or the user’s creative TASTE (e.g. “audience is first-time homebuyers”, “prefers bold lime accents”, “always captions off”) into the workspace Memory so it shapes FUTURE ads. For lasting things, not one-off requests. Merges into the existing Memory (never overwrites); de-dupes on identical text. NEVER how Hermoso, a tool, a connector or
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | profile id/name from list_brands, this call only |
| text | string | yes | the fact/preference, concise |
| category | string | no | short bucket: Brand, Audience, Taste, Do, Don’t, or Preference (default General) |
Delete a saved Memory item by its id (from list_memory). Records a cross-device delete so it doesn’t come back. Minor + re-creatable (you can remember it again), so no confirm needed.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | profile id/name from list_brands, this call only |
| id | string | yes | the memory item id (from list_memory) |
Clean up and consolidate the workspace Memory: drops entries that are about how Hermoso, a tool, a connector or a platform API behaves (product behaviour, not the brand), drops phone numbers and emails, and merges near-duplicate facts into one sentence each. Call with no argument to get the PROPOSAL (what would be removed and merged, with reasons) — nothing changes. Call again with confirm:true to
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | profile id/name from list_brands, this call only |
| confirm | boolean | no | true to APPLY the proposal; omit to only see it |
List this workspace's SWIPEFILE — the saved-ad research board: every named collection and the ads/creatives kept in it (advertiser, headline, body copy, media URL, platform, when it was saved, and any taste tags). The SAME board the web app's Swipefile tab shows. Use it to answer "what have we saved?", to mine the user's own taste before planning an ad, or to find a reference to remix. Read-only,
| Parameter | Type | Required | Notes |
|---|---|---|---|
| collection | string | no | only list ads in this collection (by name or id) — omit for every collection |
| limit | number | no | max ads to return (default 50, max 500) |
Save one or more ads/creatives to a named SWIPEFILE collection, creating the collection if it does not exist — the headless twin of the heart on every ad card in the web app. Saved ads persist to the workspace board the web Swipefile tab shows, and feed the taste signal every future ad is planned against. De-dupes: re-saving the same ad (same key/link/media) MOVES it into the named collection inst
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | profile id/name from list_brands, this call only |
| collection | string | yes | the collection name — an existing one, or a new one to create |
| items | array | yes | the ads to save |
Set the OUTREACH STATUS and/or a NOTE on a creator already saved in the swipefile. Status is one of new | contacted | replied | booked | passed. The note is free text (deal terms, rate, what was sent). Reads back the updated row. Use list_swipefile to find the key. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | profile id/name from list_brands, this call only |
| key | string | yes | the saved row's key from list_swipefile, e.g. tiktok:handle |
| status | string | no | outreach status: new, contacted, replied, booked or passed |
| note | string | no | replaces the existing note; pass "" to clear it |
List the PLAYBOOKS saved in this workspace — the reusable strategy cards (winning hooks, angles, formats and the concrete plays to run) kept from teardowns, angle mining and creatives worth repeating. The same Playbooks the web app's Playbooks tab lists. Read one before planning an ad so you re-run what already worked instead of starting cold. Read-only, free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| limit | number | no | max playbooks to return (default 25, max 100) |
| full | boolean | no | true to return every hook/angle/play in the text, not just the headline counts |
Save a reusable PLAYBOOK — the strategy takeaways worth re-running: the hooks that work, the angles, the formats, and the concrete plays. Lands in the same Playbooks library the web app lists, runs and manages. Distinct from save_skill (a directive applied to every ad) and from the swipefile (raw saved creative). Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | profile id/name from list_brands, this call only |
| name | string | yes | the playbook headline — what it is, in a few words |
| hooks | array | no | the opening hooks worth reusing, verbatim |
| angles | array | no | the persuasion angles ({title, detail}) |
| formats | array | no | the formats/recipes this plays best in (e.g. ugc_selfie, cinematic, static) |
| plays | array | no | the concrete plays to run ({title, detail}) — the actionable half |
| source | string | no | where it came from, e.g. “teardown · Ridge” |
Delete a saved playbook by id (from list_playbooks). Records a cross-device delete so it does not come back on the next sync. Minor + re-creatable, so no confirm needed.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | profile id/name from list_brands, this call only |
| id | string | yes | the playbook id (from list_playbooks) |
List this workspace’s SAVED CREATORS — the reusable on-camera cast (AI creators made here, a person pulled from a social profile, a consented photo upload). Read-only, FREE. Each entry gives the name, the PORTRAIT URL, where the portrait came from and whether a real person’s likeness consent is on file, how many extra pose plates exist, and any chosen or cloned voice. That casts them for the whole
| Parameter | Type | Required | Notes |
|---|---|---|---|
| limit | number | no | max creators to return (default 24) |
| gender | string | no | filter the PRESET creators by gender (the saved cast is never filtered) |
Add a portrait to this workspace’s reusable CAST so the SAME person can star in future ads — the headless twin of the app’s + > Pick a creator > save. Pass the portrait’s public url plus a name to call them by. Saving is FREE and renders nothing. LIKENESS: leave source "generated" for an AI-made person (free on every plan) and use "upload"/"social" for a REAL person. Using a real person’s face,
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | profile id/name from list_brands, this call only |
| name | string | yes | what to call this creator (e.g. “Sarah”) — list_creators and the app’s picker match on it |
| image | string | no | REQUIRED except with useAnyway. public https url of the portrait (an existing render’s url, or any public photo). Not a local file path — upload it with upload_file first and save the url that returns |
| source | string | no | "generated" (default) = an AI-made person; "upload" / "social" = a REAL person |
| voice | string | no | a default voice name for this persona (engines + voices are in hermoso_capabilities) |
| poses | array | no | up to 4 extra full-body / angle plates of the SAME person (public urls) — they make a wider shot hold the identity |
| look | string | no | their canonical wardrobe/appearance in words — reused to hold the look steady across ads |
| useAnyway | boolean | no | Only for a creator whose saved photo was flagged too unclear to cast: true casts the current photo as it is, no new image needed |
Remove a saved creator from this workspace’s cast by id (from list_creators). Records a cross-device delete so they don’t reappear on the user’s other devices. It only drops the roster entry — ads already rendered with that person are untouched — and the same portrait can be saved again with save_creator, so no confirm is needed.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | profile id/name from list_brands, this call only |
| id | string | yes | the creator id (from list_creators) |
Patch SPECIFIC fields of the active profile (name, domain, sells, summary, category, audience, positioning, voice, style, goal, logos, brand/product name pronunciations, profileType: explore = kept but unused) WITHOUT overwriting the rest — a read-modify-write on the saved brand. Use for “change our voice to playful”, “we sell to dentists now”. To onboard a brand from scratch, use draft_brand. If
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | profile id/name from list_brands, this call only |
| name | string | no | brand name |
| domain | string | no | website domain |
| sells | string | no | what the brand sells |
| summary | string | no | one-line description |
| category | string | no | product or business category, one line |
| audience | string | no | who the brand sells to, one line |
| positioning | string | no | how the brand is positioned against alternatives, one line |
| voice | string | no | brand voice/tone |
| style | string | no | visual style — palette, typography, aesthetic |
| goal | string | no | current marketing goal |
| pronounce | string | no | how the brand NAME is said aloud, as a simple respelling with the stressed syllable in capitals (e.g. "KOH-dee-ak"). Videos use it as a delivery note beside the spoken line; set it when a render mispr |
| pronunciations | object | no | how PRODUCT names are said aloud, e.g. {"Power Cakes": "POW-er cakes"}. Merged into the saved ones; an empty string removes one. |
| logo | string | no | logo image: URL or local file path to a PNG, JPEG or WebP (no SVG) |
| logoLight | string | no | for dark pictures; "none" removes |
| logoDark | string | no | for light pictures |
| profileType | string | no | website, social, describe or explore; explore keeps the profile but stops using it |
Read one of this workspace’s data stores by key, for visibility into what the app holds — playbooks, swipefile, saved locations, avatars, creations, chats, brand, memory, skills. Read-only, free. Allowed keys: heist.memory.v1, heist.skills.v1, heist.playbooks.v1, heist.avatars.v1, heist.locations.v1, heist.chats.v1, heist.creations.v1, heist.assets.v1, heist.brand.v1, adInspo.swipefile.v1. (The ty
| Parameter | Type | Required | Notes |
|---|---|---|---|
| key | string | yes | the store key to read (one of the allowlisted keys) |
| limit | number | no | max array items to return (default 50) |
Read this account's app settings — the LANGUAGE Hermoso writes ads and copy in (chat replies follow the language the user writes in), the app appearance (theme), and whether the weekly competitor-watch email is on. Same settings as the web app's Settings pane. Read-only, free.
No parameter schema on file.
Change this account's app settings. language = the language EVERY ad, script and plan is written in from now on (chat replies follow the language the user writes in) (say the language in plain English, e.g. "German", "Japanese", "Brazilian Portuguese") — it applies to renders made over MCP as well as in the app. theme = the app's appearance, "dark" or "light". watchEmail = the weekly competitor-wa
| Parameter | Type | Required | Notes |
|---|---|---|---|
| language | string | no | language for generated ads and copy (chat replies follow the language the user writes in) — e.g. "English", "German", "Japanese" |
| theme | string | no | app appearance |
| watchEmail | boolean | no | weekly competitor-watch email on/off |
List the third-party accounts connected to this workspace (Meta, Google Ads, Google Drive/Sheets/Docs, YouTube, LinkedIn, OneDrive, Slack, …) — provider, status and the connected account label — PLUS which providers are available to connect. IT ALSO FLAGS A CONNECTION WHOSE PERMISSIONS ARE OUT OF DATE: a provider writes its granted permission set into the token at consent time, so a connection aut
No parameter schema on file.
Where this brand's ALERTS go and whether they are arriving: for each alert channel (Slack, Discord, a generic webhook) whether it is connected, which event groups it receives (renders, posts, approvals, watch, credits, leads — default all), its health (delivering / NOT DELIVERING / stopped because the channel was deleted or revoked — reconnect by pasting a new URL in the app), whether the webhook
No parameter schema on file.
Choose which event groups one alert channel receives. provider: slack, discord or webhook. events: any of renders (a render finished or failed), posts (a scheduled post published, partly published or failed), approvals (autopilot drafts waiting for review), watch (new competitor ads found by the competitor watch), credits (credits running low), leads (a new lead-form lead from Meta, LinkedIn or Sn
| Parameter | Type | Required | Notes |
|---|---|---|---|
| provider | string | yes | alert channel to configure: slack, discord or webhook |
| events | yes | event groups, or "all" |
Send a test alert to one alert channel (provider: slack, discord or webhook) or to every connected one, and report what each answered (delivered, or the HTTP status and the channel's own words). A test that gets through also clears a NOT DELIVERING or stopped state. The webhook test is signed like every event. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| provider | string | no | omit to test every connected alert channel |
Create (or replace) the signing secret for this brand's generic alert webhook and return it ONCE — it is never readable again, so hand it to the user to store. Every event carries Standard Webhooks headers: webhook-id, webhook-timestamp and webhook-signature = v1,<base64 HMAC-SHA256 of "id.timestamp.body"> keyed by the base64 part after whsec_. On a rotation the OLD secret keeps signing alongside
No parameter schema on file.
Show every identity a connected account can act as, and which ones this PROFILE is currently allowed to use — Facebook Pages + Instagram + Meta ad accounts, Google Ads customers, LinkedIn company Pages (and the personal profile), Pinterest ad accounts, Microsoft Advertising accounts. One person often administers several; only the ticked ones can be posted to or spent from. Call this before set_con
| Parameter | Type | Required | Notes |
|---|---|---|---|
| provider | string | yes | which connector’s accounts to list |
Set WHICH of a connector's accounts this profile is allowed to post to and spend from — Facebook Pages / Instagram / Meta ad accounts, Google Ads customers, LinkedIn company Pages (and the personal profile), Pinterest or Microsoft Advertising ad accounts. Pass ids from list_connector_accounts. This REPLACES the current selection: anything you leave out is un-shared, and an EMPTY list shares nothin
| Parameter | Type | Required | Notes |
|---|---|---|---|
| provider | string | yes | which connector to scope |
| accountIds | array | yes | the ids (from list_connector_accounts) this profile may use — an empty array shares nothing |
Disconnect a third-party account from this workspace (Meta, Google Ads, Google Drive/Sheets/Docs, YouTube, TikTok, LinkedIn, X, Reddit Ads, Pinterest, Google Business, Microsoft Advertising/OneDrive, Slack, …). This always drops the stored credentials, so every tool for that provider stops working immediately and posts/campaigns already published are NOT affected. IT NEVER REVOKES THE GRANT AT THE
| Parameter | Type | Required | Notes |
|---|---|---|---|
| provider | string | yes | provider id exactly as list_connectors reports it, e.g. "meta", "google_ads", "youtube", "linkedin" |
| confirm | boolean | no | REQUIRED true — reconnecting a sign-in account afterwards needs the user's browser; a paste-a-key account is reconnected with connect_connector |
| account | string | no | on a channel with several connected accounts (TikTok, X, YouTube, Threads, Bluesky, Telegram, Pinterest): remove ONLY this account (@handle or id from list_connector_accounts) and keep the others |
Connect an account that links with a KEY rather than a sign-in screen, from here, with no browser: AppLovin Ads, Stripe, Intercom, Mailchimp, Klaviyo, Vercel, ChatGPT Ads, Apple Ads, Bluesky, Telegram, Bing Webmaster Tools, PostHog, Mixpanel, Amplitude, WordPress, Slack, Discord, Webhook. Pass provider and fields in that provider's own field names (listed below, * = required). The key is checked l
| Parameter | Type | Required | Notes |
|---|---|---|---|
| provider | string | yes | the connector id: applovin_ads, stripe, intercom, mailchimp, klaviyo, vercel, openai_ads, apple_ads, bluesky, telegram, bing_webmaster, posthog, mixpanel, amplitude, wordpress, slack, discord, webhook |
| fields | object | no | that provider's own field names and values, e.g. {"apiKey":"…"}; the names for each provider are in the description |
On a connector that several teammates can contribute their OWN account to (see list_connectors — the row reports multiContributor), remove YOURS from this profile: your stored credential is dropped and the accounts you shared stop being shared. Your teammates' accounts on the same connection keep working, and nothing changes at the provider — reconnecting in a browser shares again. Use this instea
| Parameter | Type | Required | Notes |
|---|---|---|---|
| provider | string | yes | provider id exactly as list_connectors reports it, e.g. "linkedin", "tiktok_ads", "meta" |
List the members of the current profile — email, role (admin/member) and status. Read-only, free.
No parameter schema on file.
Invite someone to this profile by email (role: member = read-only on billing, admin = full). This SENDS a real email invite / share link — an account change. Confirm the exact email + role with the user, then call with confirm:true.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| string | yes | the invitee’s email | |
| role | string | no | default member |
| confirm | boolean | no | REQUIRED true — this invites a real person |
Remove a member from this profile by email — they lose access (you can re-invite them later), AND every connection THEY made on this profile is disconnected with them: their own X, LinkedIn, TikTok, YouTube, Pinterest, Threads… connections (on a channel holding several accounts, only the accounts they added), and any extra login or authorization they added. Connections the owner or anyone else mad
| Parameter | Type | Required | Notes |
|---|---|---|---|
| string | yes | the member’s email | |
| confirm | boolean | no | REQUIRED true |
Change a workspace member’s role — admin (full access incl. billing) or member (read-only on billing). A privilege change: confirm the exact person + new role with the user, then call with confirm:true.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| string | yes | the member’s email | |
| role | string | yes | the new role |
| confirm | boolean | no | REQUIRED true |
List the most recent render jobs + how many are currently running, so you can report on or resume in-flight work.
No parameter schema on file.
The errors actually recorded against this workspace, GROUPED by fingerprint — the same failure at the same call site is one row with a hit count and first/last seen, sorted defects-first. Each row says whose side it is: ours (a defect worth fixing), user (a refusal we deliberately authored, e.g. not-connected or out-of-credits), or unknown (a vendor 4xx we cannot attribute — never guessed).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| kind | string | no | 'ours' = a defect; 'user' = a refusal we authored; 'unknown' = we could not tell |
| surface | string | no | where it happened: http (an API route), mcp (an agent tool), agent (the in-app Studio agent), job (an async render/publish), client (a browser crash) |
| since | string | no | ISO timestamp — only groups last seen at or after this |
| limit | number | no | how many groups to return (default 50, max 200) |
One error group in full by fingerprint (from list_errors): every field, plus the most recent redacted occurrences — status, connector, job id, workspace, and a shape-only echo of the inputs. This is what makes a bug reproducible. Read-only, 0 credits.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| fingerprint | string | yes | the fp value from list_errors |
What Hermoso ALREADY KNOWS for the active profile: the same saved profile (products, logos, palette, positioning) + learned memory the web Studio uses. Call it when you need to know whether a brand is on file: if hasBrand is true you can omit brand everywhere; if false, onboard with draft_brand. Not a required first step before a render: the create tools read the saved brand by themselves. 0 credi
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | profile id/name from list_brands, this call only |
IMPORTANT: a domain can resolve to a DIFFERENT company than intended (e.g. Bala.com is an engineering firm, not the Bala fitness brand at shopbala.com). Before spending any credits on research or renders, VERIFY the returned name (and summary) match the brand the user meant; if it looks wrong, re-draft with the correct domain or a description (pass save:false until confirmed) — this tool canno
| Parameter | Type | Required | Notes |
|---|---|---|---|
| domain | string | no | a website to scrape, or a social link (profile, YouTube channel or one post) to draft from the account it names |
| description | string | no | a free-text brand description (no website) |
| socialHandle | string | no | a social handle (creators); needs platform, never guessed |
| platform | string | no | socialHandle platform: instagram/tiktok/youtube/twitter/threads |
| save | boolean | no | Default: saves only when NO brand is saved yet; pass true to REPLACE the saved profile (the drafted fields overwrite the saved ones), false to never save |
| confirmed | boolean | no | user confirmed it is their brand: read + save site product photos (paid) |
Browse this workspace's Library — every image/video generated in the Studio, and uploads (source:'upload'), newest first (the same Library the web app shows). Returns served URLs you can open directly or hand to fetch_asset for a download link, plus each asset's id, a human TITLE (the ad's headline or concept, the prompt's first words, or the upload's name), kind, model, and age. When the user nam
| Parameter | Type | Required | Notes |
|---|---|---|---|
| kind | string | no | filter by asset kind (default 'all') |
| uploads | boolean | no | — |
| query | string | no | only assets whose title contains this text (case-insensitive), e.g. an ad headline the user quoted |
| limit | number | no | max assets to return (default 20, max 60) |
PERMANENTLY delete images/videos from this brand's Library: the entry and the stored file. Pass ids (L0… from list_library) or urls. Without confirm it lists what would be deleted; ask the user, then call again with confirm:true. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| ids | array | no | Library ids from list_library |
| urls | array | no | file links |
| confirm | boolean | no | true deletes |
Resolve a generated asset reference (a /generated/… path or any URL) to a clickable absolute URL + a direct download URL.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| url | string | yes | the asset url or /generated/ path |
| name | string | no | optional filename for the download |
The brand's product catalog: each product's id, title, page link, price (and whether it is current), photos, its own selling points and audience, and when it was last checked against its live page. Free. refresh:'stale' re-reads rows older than a day first, refresh:'all' re-reads every page.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| refresh | string | no | re-read product pages before listing: 'stale' (older than a day) or 'all' |
| brandId | string | no | a profile id/name from list_brands; omit for the active profile |
List the product photos ALREADY saved in your workspace — the brand's product library plus any app-store screens (also surfaces photos locked in your OTHER creations, since a set product lands in the shared library). FREE — returns each photo's url + the product it shows (the shop's own title when one is on file). These are already saved: never ask the user to upload them again. Reads YOUR saved b
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brandId | string | no | a profile id/name from list_brands whose product library to list; omit to use the active profile |
List the connected Facebook Page's or Instagram account's OWN existing posts — id, caption, permalink, publish date and format. THIS IS THE TOOL THAT GETS YOU THE postId every other Meta read needs: meta_post_insights, list_meta_comments and manage_meta_post all require one. Use it for "how did our last few posts do", to find a post the user describes loosely, or before backfill_posts. Pass target
| Parameter | Type | Required | Notes |
|---|---|---|---|
| tagged | boolean | no | INSTAGRAM — list posts OTHER accounts tagged the brand in (UGC, creator posts, partner mentions), with each author’s @handle, instead of the brand’s own posts. Free. |
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| target | string | no | default facebook; 'instagram' reads the Page's linked IG business account |
| account | string | no | which Instagram account — an @handle or id from list_connector_accounts("instagram"). Needed when several are linked, and the way an Instagram Login (standalone) account is reached; omit for the one P |
| pageId | string | no | which connected Page — omit when the profile has only one |
| limit | number | no | how many posts (default 25, max 100) |
| cursor | string | no | paging cursor returned by a previous call |
| includeUnpublished | boolean | no | Facebook only — also return unpublished drafts (hidden by default) |
List every post Hermoso has recorded publishing for this brand, newest first, across all channels — channel, permalink, caption, format, the HOOK and SUBJECT it was written to, and its measured engagement. The WHOLE history, no cap: pass the reply's nextCursor as cursor for older posts. Each row says how it was recorded: 'captured' (written at publish time — the hook is what the author intended)
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| channel | string | no | filter to one channel: facebook, instagram, threads, x, linkedin, youtube, tiktok, pinterest, bluesky, google_business |
| limit | number | no | max posts (default 50, max 200), newest first |
| cursor | string | no | nextCursor from a previous reply: the next, older page |
Use this when the user asks which of their social posts did best. Aggregate this brand's published posts to answer WHICH HOOKS AND SUBJECTS WORK. Groups by hook (default), subject, format (recipe), channel, media format or posting hour, reports the engagement RATE within each channel, and ranks the best and worst POSTS in each channel. Describe a post by the creative it carried (what it shows, its
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| axis | string | no | what to group by — default hook; recipe = the format of the creative |
| channel | string | no | restrict to one channel |
| days | number | no | look back N days (1-730) over the whole history; omit for the recent posts only |
WHAT TO FIX NEXT, post by post — and the tool that fixes it. Post_performance tells you which hook is AHEAD; this tells you what is WRONG with a given post and where the next edit goes. Under-distributed is A HOOK PROBLEM. Seen but not held is A RETENTION PROBLEM. Seen, held, and still not converting is AN OFFER PROBLEM. FOUR REFUSALS, AND YOU SHOULD REPEAT THEM RATHER THAN PAPER OVER THEM: (1) a
| Parameter | Type | Required | Notes |
|---|---|---|---|
| channel | string | no | restrict to one channel: facebook, instagram, threads, x, linkedin, youtube, tiktok, pinterest, bluesky |
| limit | number | no | how many recent posts to diagnose (default 25, max 200). The baseline is always built from EVERY post recorded for the brand, never only these, so a bad month can never become its own definition of no |
| converting | boolean | no | pass false ONLY when the user has told you these posts are getting seen and are not converting — it re-reads the ones that are earning their reach as an offer problem instead of a win. Omit when you d |
Fetch fresh performance numbers for this brand's recorded posts and store them as a time-series. Metrics ACCRUE, so a post is read at ~24 hours and again at ~7 days; this collects whichever readings are due and skips the ones already taken. A channel that cannot report a metric records it as ABSENT with the reason — never as zero — and a read that fails is recorded as 'could not tell', which contr
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| includeMetered | boolean | no | also read X, which BILLS CREDITS per post read — ask the user first |
| max | number | no | cap how many posts to read in this run (default 40) |
| remeasure | boolean | no | ALSO re-read posts older than 7 days whose every reading came back empty or failed — use after post_performance reports posts "read but empty", or once a channel's reader has been fixed. Otherwise tho |
Import this brand's PAST posts from a channel into the performance record, so 'which hook works' can draw on history rather than only on what was published since Hermoso started recording. Supports facebook, instagram, threads, youtube, tiktok, pinterest and bluesky; the others say plainly why they cannot (LinkedIn has no enumerate-my-posts endpoint on our grant, X bills per read so it is excluded
| Parameter | Type | Required | Notes |
|---|---|---|---|
| channel | string | yes | which channel to import from |
| confirm | boolean | no | actually import — omit for a dry run that only quotes the cost |
| limit | number | no | how many posts this page (default 50, max 200) |
| cursor | string | no | resume from a previous run |
| accountRef | string | no | which Page / account, when the profile has more than one |
- Every tool — any call limit is its publisher’s, not ours
- No card, and mcprush charges nothing for it
- Runs at its publisher’s own address — this marketplace is not in the path
- Nothing to cap, because mcprush bills nothing
What counts against your monthly calls
| Tool | Unit | Calls used | Out of the allowance |
|---|
Nothing here is billable: mcprush charges nothing to install Hermoso: Social Media Manager or to call it, at any volume.
Two independent axes, because powerful and malicious are different questions. The grade is threat only. The capability level is blast radius, and it is never a penalty on the grade — it is priced as one subtract-only term in the score, where you can see it.
This listing is an address its publisher runs, and you connect to it directly — this marketplace is not in the path. The scanner reads packages and public source, and an address is neither: the repository linked on this page is where this record was collected from, not a copy of what the address serves, so it is not graded in its place. So there is no grade — not a withheld one, an unmeasured one.
Release history
This is an address its publisher runs: which release it serves is theirs to choose, so there is nothing here to pin. The history below is for reading; the current release on file is 1.0.0.
No release note was published with this version.
One review per account, from a signed-in account that does not publish this listing. Nothing else is asked — you can say what you think before you install it. Publishers can reply once per review, and a review can be edited or deleted by whoever wrote it.
Any signed-in account that does not publish Hermoso: Social Media Manager can review it, once — before installing it or after. A review from an account that has installed it is marked as one.
Nobody has reviewed this listing. The rating on the card is the mean of the reviews written here and nothing else, so there is no rating until somebody writes the first — which takes a signed-in account that does not publish it, and nothing else.