Publish organically to the channels a brand has connected, or queue posts on a schedule.
WHOP
post_to_whop#
Post to Whop destructive
Use this when the user wants to post into their WHOP (the community they sell on Whop). Publishing costs no credits. A FORUM post takes a title, Markdown text, images and video, pinning, members-only or public visibility, a paywall and a poll; a CHAT message takes text and files. It posts as the Hermoso app the whop installed. experienceId picks the forum or chat (list_whop_destinations names them); with one forum or chat it is used, with several and none named the call is refused and lists them. Mentions are OFF unless mention:true (it notifies every member). Returns the post id for edit_whop_post / delete_whop_post / get_whop_post. Not connected: the seller connects the whop from Hermoso (Connectors ▸ Whop) or presses Open Hermoso in the whop's dashboard; nothing is billed through Whop.
| Parameter | Type | Description |
|---|
brand | string | 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 REFUSED and nothing is posted. |
hook | string | the post's angle — a list_hooks id or your own wording, reused exactly |
subject | string | what the post is about |
recipe | string | the post's FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format |
ideaId | string | short id of the content-plan idea this post came from |
idempotencyKey | string | any stable string: a repeat within 24h returns the original post instead of posting again |
allowDuplicate | boolean | post it even though an identical post was just made |
experienceId | string | the forum or chat (exp_…) from list_whop_destinations |
text | string | the post or message, Markdown |
title | string | forum posts: the headline (required with a paywall) |
imageUrl | string | |
imageUrls | string[] | up to 10 files |
videoUrl | string | |
pinned | boolean | forum posts: pin to the top |
mention | boolean | forum posts: notify every member (off by default) |
visibility | "members_only" | "globally_visible" | forum posts: who can see it |
paywallAmount | number | forum posts: price to unlock, e.g. 5 for $5 (needs a title) |
paywallCurrency | string | paywall currency, default usd |
poll | string[] | 2 to 10 poll options |
replyTo | string | reply to this post or message id |
list_whop_destinations#
List Whop forums and chats read-only
List the forums and chats in the brand's whop that Hermoso can post into, each with its experienceId and kind (forum or chat). Free.
| Parameter | Type | Description |
|---|
brand | string | 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 CALL ONLY. A name that matches no profile, or two, is REFUSED and nothing is done. |
list_whop_products#
Read Whop products read-only
Read the brand's Whop products as Hermoso sees them: names, prices and product photos, plus the brand description Hermoso drafts from them (draft_brand with that description sets the brand up). Free.
| Parameter | Type | Description |
|---|
brand | string | 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 CALL ONLY. A name that matches no profile, or two, is REFUSED and nothing is done. |
edit_whop_post#
Edit a Whop post destructive
Change a Whop forum post (text, title, pinned, visibility) or chat message (text, pinned) that Hermoso posted. Free.
| Parameter | Type | Description |
|---|
brand | string | 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 CALL ONLY. A name that matches no profile, or two, is REFUSED and nothing is done. |
idrequired | string | the id post_to_whop returned |
kindrequired | "forum" | "chat" | |
text | string | |
title | string | |
pinned | boolean | |
visibility | "members_only" | "globally_visible" | |
delete_whop_post#
Delete a Whop chat message destructive
PERMANENTLY delete a Whop CHAT message Hermoso posted. Call it WITHOUT confirm first: nothing is deleted and you get a sentence quoting the message to show the user. Whop's API cannot delete a FORUM post; asked to, it says so and points to Whop. Free.
| Parameter | Type | Description |
|---|
brand | string | 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 CALL ONLY. A name that matches no profile, or two, is REFUSED and nothing is done. |
idrequired | string | |
kindrequired | "forum" | "chat" | |
confirm | boolean | true only once the user agreed |
get_whop_post#
Read a Whop post read-only
Read back a Whop forum post or chat message Hermoso posted: its text, the numbers Whop reports (views, likes and comments on a forum post; views and reactions on a chat message), and whether it is still up — state live, deleted or unconfirmed. Whop's read also returns a DELETED chat message, so pass the chat's experienceId (from list_whop_destinations) to confirm a message is still in the chat; one deleted through Hermoso always reads deleted. A count Whop does not report is listed as unavailable, never zero. Free.
| Parameter | Type | Description |
|---|
brand | string | 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 CALL ONLY. A name that matches no profile, or two, is REFUSED and nothing is done. |
idrequired | string | |
kindrequired | "forum" | "chat" | |
experienceId | string | the chat it was posted in: confirms a chat message is still up |
list_inbox#
One inbox for comments, replies, mentions, reviews and DMs read-only
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 and Telegram. Support tickets too: the open conversations in the brand's Intercom inbox. Use this for 'what do I need to reply to', 'any new comments', 'any new DMs', 'how are people responding'. Each item carries a composite id you hand straight to reply_to_inbox_item. A channel that is not connected is skipped silently; a channel that FAILS to read is named in `notes` rather than dropped, so a short list is never mistaken for a quiet week. Each DM conversation folds to ONE item (the newest thing the other person said). Messenger and Instagram DMs need no argument; each has a messageKind (text, story_mention, story_reply, share, image, video...), a spoken summary, and unavailable:true when Meta withholds the content. Bluesky and X DMs need conversationId (list_bluesky_convos / list_x_dms). FREE ON EVERY CHANNEL EXCEPT X, which bills per API call — X mentions and X DMs cost credits per item returned, and every other source only re-reads what the per-channel tools already read.
| Parameter | Type | Description |
|---|
sources | string[] | 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, intercom. Omit for every connected channel. |
postId | string | Restrict Facebook/Instagram to one post or media id (Meta reads comments per post). |
videoId | string | Restrict YouTube to one video. |
conversationId | string | 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 | 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 | Max items per source (default 25). |
unansweredOnly | boolean | Only items with no reply from the brand yet. |
reply_to_inbox_item#
Reply to anything in the inbox
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 customer from the brand's support inbox) — so show the user the exact wording and get their go-ahead first either way. X is the one exception and says so: a reply there is a new post, so it goes through post_to_x with replyTo.
| Parameter | Type | Description |
|---|
idrequired | string | 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. |
textrequired | string | The reply, exactly as it should appear publicly. |
hermoso_capabilities#
Hermoso models, options and credit costs read-only
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. It is NOT a prerequisite for rendering: generate_image, generate_video and render_ad all run with `model` omitted and route to the server’s own default. Read-only, free.
No parameters.
hermoso_credits#
Credit balance read-only
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). Check before kicking off paid generation; answer "does posting cost credits?" with NO. HOW MANY posts is set by the plan, not credits: the Free plan includes 10 posts a month on up to 3 channels, and a post counts once however many channels it goes to (`posting` says how many are used); every paid plan posts without a limit and runs autopilot (Basic $9.99/mo: unlimited posting and Autopilot, 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: media models at cost + 10% (memberRate); Basic and Free pay standard prices.
No parameters.
buy_credits#
Buy credits
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 yet, you get a Stripe checkout URL to hand your human for the FIRST purchase; their card saves for one-click after that. Packs only; subscriptions are managed by a person in Billing. IF YOU ARE AN AGENT HOLDING YOUR OWN PAYMENT CREDENTIAL, there is a third path that needs no human at all: `POST /api/billing/machine-payment` with a `packId` answers HTTP 402 carrying an MPP challenge, and grants the pack once you authorise and retry with the credential — the same packs, the same prices, the same credits. `GET /api/billing/config` carries a `machinePayments` block listing the packs with their per-credit rates and saying whether that lane is enabled on this server. Most agents do NOT have their own credential yet, so the checkout link above remains the normal path. To stop running out entirely, turn on auto-reload with set_auto_reload (admin) — low balances then top themselves up from the saved card automatically.
| Parameter | Type | Description |
|---|
pack | string | the pack id to buy (e.g. pack-2k) — omit to list the available packs first |
confirm | boolean | 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 | 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 |
YOUTUBE CHANNEL SECTIONS + PLAYLIST IMAGES
manage_youtube_channel_section#
Channel homepage sections destructive
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 not a draft. `type` decides what the shelf holds — singlePlaylist and multiplePlaylists take playlist ids, multipleChannels takes channel ids and those two plus multiplePlaylists take a title you choose, while popularUploads, recentUploads, subscriptions and the rest are filled by YouTube and take neither. `position` is zero-based and is what re-orders the page. DO NOT TREAT A LIST AS A READ-BACK: YouTube's own section list lags a write by a few seconds in BOTH directions — measured, it returned nothing right after a create and still returned a deleted section right after a delete — so a write reports what YouTube returned and a delete reports as ACCEPTED, and re-listing straight away can show the old layout. Free.
| Parameter | Type | Description |
|---|
action | "list" | "create" | "update" | "delete" | defaults to 'list' |
type | string | singlePlaylist | multiplePlaylists | popularUploads | recentUploads | likes | allPlaylists | likedPlaylists | recentPosts | recentActivity | liveEvents | upcomingEvents | completedEvents | multipleChannels | postedVideos | postedPlaylists | subscriptions. Required to create or update |
title | string | the heading, and only multiplePlaylists and multipleChannels take one — YouTube writes the heading for every other type |
playlists | string[] | playlist ids, from list_youtube_playlists. Required for singlePlaylist (exactly one) and multiplePlaylists |
channels | string[] | channel ids to feature. Required for multipleChannels |
position | number | zero-based position on the homepage. Leave it off and YouTube places the section |
style | "horizontalRow" | "verticalList" | leave it off to let YouTube choose |
sectionId | string | for update and delete — from action:'list' |
hl | string | language for the returned titles, e.g. "en" |
confirm | boolean | must be true to actually delete |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
manage_youtube_playlist_image#
Custom playlist cover image destructive
PUT A CUSTOM COVER ON A PLAYLIST — the last call in a chain that already existed, since make_thumbnail renders the artwork and the playlist tools own the 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 — a Hermoso render, a make_thumbnail result, or the user’s OWN artwork brought in with upload_file, which turns any local or external file into a URL this accepts. 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 would report a missing playlist id as a Hermoso outage — the refusals here are the real ones. If it comes back refused, the first thing to check is CHANNEL VERIFICATION: custom imagery needs a verified YouTube channel (add a phone number at youtube.com/verify), and on an unverified channel the sibling call that sets a custom video thumbnail is refused in the same way. Free.
| Parameter | Type | Description |
|---|
playlistIdrequired | string | from list_youtube_playlists |
action | "list" | "set" | "delete" | defaults to 'list' |
imageUrl | string | for action:'set' — a Hermoso render or make_thumbnail URL |
imageId | string | for action:'delete' — from action:'list' |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
list_youtube_captions#
List (and read) a video’s caption tracks read-only
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 cannot be edited or deleted. Downloading is also the fastest way to get an existing video’s full script back for repurposing. Read-only, 0 credits. Needs a connected YouTube channel.
| Parameter | Type | Description |
|---|
videoIdrequired | string | the YouTube video id — captions are listed per video |
download | string | a captionId or a language code to also download as text |
format | "srt" | "vtt" | "sbv" | download format, default srt |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
manage_youtube_caption#
Upload, replace or delete a caption track destructive
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 through any API; to replace it, upload your own track in the same language and YouTube prefers yours. Delete is confirm-gated — viewers with captions on lose them immediately and YouTube stops indexing the video by that text, though it can be re-uploaded if you still have the file. 0 credits. Needs a connected YouTube channel.
| Parameter | Type | Description |
|---|
actionrequired | "upload" | "update" | "delete" | upload | update | delete |
videoId | string | required for upload; strongly recommended for update and delete so the track can be resolved and read back |
captionId | string | required for update and delete (from list_youtube_captions) |
text | string | the caption file body — SRT or WebVTT. Required for upload. |
language | string | BCP-47 code such as en, es, pt-BR. Required for upload. |
name | string | a label shown in YouTube’s caption picker |
draft | boolean | true keeps the track unpublished while you review it |
confirm | boolean | required to delete |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
list_youtube_categories#
List the video categories you can publish under read-only
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. Needs a connected YouTube channel.
| Parameter | Type | Description |
|---|
regionCoderequired | string | ISO 3166-1 alpha-2 country code — US, GB, DE. YouTube’s category ids differ by country, so this cannot be guessed. |
includeUnassignable | boolean | also return categories YouTube will refuse on upload (default false) |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
google_business_search_keywords#
What people searched to find this business read-only
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 withholds an exact count for them and returns only an upper bound, so those rows come back with impressions=null and below=<threshold>. Report those as “fewer than N” — NEVER as zero and never as the threshold itself, both of which are numbers a marketer would act on and neither is true. Counts are UNIQUE USERS per month summed across the window, not impressions; the two are not comparable. Google keeps roughly 12 months of history. Read-only, 0 credits. Needs Google Business Profile connected.
| Parameter | Type | Description |
|---|
locationId | string | which listing, when the profile has more than one (list_business_locations) |
months | number | how many whole months back, default 3, cap 12. The current month is excluded because it is always partial. |
limit | number | max terms, default 100, which is also Google’s maximum |
pageToken | string | nextPageToken from a previous call |
list_bluesky_posts#
List the brand's own Bluesky posts read-only
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 went out, its web URL, its live like/repost/reply/quote/bookmark counts, and whether it is a REPOST of someone else's post or a reply — a repost is not the brand's own creative and must not be reported as its performance. Optional filter: posts_no_replies, posts_with_media, posts_with_replies, posts_and_author_threads (an unknown one is refused by name). Bluesky publishes NO impression or view count in any lexicon, so these are counts with no denominator and no engagement rate can be computed from them. Read-only, 0 credits.
| Parameter | Type | Description |
|---|
brand | string | 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 CALL ONLY. A name that matches no profile, or two, is REFUSED and nothing is done. |
limit | number | how many posts, 1–100 (default 25). 100 is Bluesky's own maximum. |
cursor | string | nextCursor from a previous call — a short page is NOT end-of-feed |
filter | string | posts_no_replies | posts_with_media | posts_with_replies | posts_and_author_threads |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
search_x#
Search X (Twitter) read-only
Keyword search across the PUBLIC X index — the last 7 days. This is the X half of ad-spy research, alongside search_tiktok / search_instagram / search_reddit / search_threads / search_youtube. 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 needs a tier we do not hold, and an older window is refused by name rather than silently truncated. Costs a small number of credits (X bills per post read and per distinct author returned with them, and this bills the ACTUAL count returned, never the number asked for). Needs X connected.
| Parameter | Type | Description |
|---|
queryrequired | string | the search, X operators included — e.g. `(protein OR creatine) -is:retweet has:media lang:en` |
maxResults | number | 10–100, default 25 |
sortOrder | "recency" | "relevancy" | default relevancy |
startTime | string | ISO 8601, no earlier than 7 days ago |
endTime | string | ISO 8601 end of the search window; within the last 7 days |
nextToken | string | from a previous page |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
x_search_counts#
How often a query is posted on X read-only
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 | Description |
|---|
queryrequired | string | same operator syntax as search_x |
granularity | "minute" | "hour" | "day" | default day |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
x_trends#
What is trending on X read-only
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 zero. Needs X connected.
| Parameter | Type | Description |
|---|
woeid | string | numeric Where-On-Earth id; default 1 = worldwide |
maxTrends | number | 1–50, default 25 |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
x_follows#
Who follows an X account (or who it follows) read-only
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”). Warning: NEEDS THE follows.read PERMISSION, added 2026-08-31: an X connection authorized before that date does NOT carry it and cannot have it added — the tool says so and asks for a reconnect rather than surfacing X’s raw refusal. Default 20, max 100 per call (X bills per account returned); page with nextToken. Needs X connected.
| Parameter | Type | Description |
|---|
username | string | handle with or without the @ — omit to read the connected account |
direction | "followers" | "following" | default followers |
maxResults | number | 1–100, default 20 |
paginationToken | string | next page token from the previous reply |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
hide_x_reply#
Hide or unhide a reply on X destructive
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 without calling X). Costs credits (X bills per API call).
| Parameter | Type | Description |
|---|
replyIdrequired | string | the REPLY’s id, or its x.com link |
hidden | boolean | default true; false unhides |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
list_x_blocks#
Who the connected X account has blocked read-only
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 2026-09-15 must be reconnected once under Settings > Connectors > X. Costs credits (X bills per API call).
| Parameter | Type | Description |
|---|
maxResults | number | 1–1000, default 100 |
paginationToken | string | next page token from the previous reply |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
list_x_broadcasts#
List X live broadcasts read-only
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 connection made before then must be reconnected once under Settings > Connectors > X (the tool says so without calling X). Costs credits (X bills per call).
| Parameter | Type | Description |
|---|
kind | "all" | "live" | "scheduled" | all (default), live or scheduled |
id | string | one broadcast id (alphanumeric, up to 24 characters) |
ids | string | comma-separated broadcast ids (live kind, up to 100) |
maxResults | number | 1-100, default 25 |
paginationToken | string | next page token from the previous reply |
oldestStart | string | scheduled kind: earliest start, ISO 8601 or epoch ms |
newestStart | string | scheduled kind: latest start, ISO 8601 or epoch ms |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
manage_x_broadcast#
Schedule, update, go live on or cancel an X broadcast destructive
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-sends the rest, because X's update replaces the whole schedule). 'go_live' airs a manualPublish schedule NOW, publicly, as the brand (confirm:true). 'cancel' deletes a schedule and cannot be undone (confirm:true). X retired recurring broadcasts: schedule each occurrence on its own. WITHOUT A SCHEDULE: 'create' makes a broadcast on sourceId that posts nothing, 'publish' puts it on air (confirm:true; skipPost:true skips the announcing post), 'end' stops one for everyone (confirm:true), 'delete' removes one and its replay (confirm:true; end it first). X limits broadcasting by account (Premium or 1,000 followers) and says so. Confirm the exact broadcast, time and stream with the user before passing confirm:true; a refusal is free. 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 credits (X bills per call).
| Parameter | Type | Description |
|---|
actionrequired | "schedule" | "update" | "go_live" | "cancel" | "create" | "publish" | "end" | "delete" | schedule, update, go_live, cancel, create, publish, end or delete |
id | string | broadcast id, for every action but schedule and create |
sourceId | string | stream source id from manage_x_stream_source (schedule, create) |
startAt | string | ISO 8601 or epoch ms |
endAt | string | ISO 8601 or epoch ms; X requires one on update |
manualPublish | boolean | required on schedule: true waits for go_live, false auto-publishes at startAt |
title | string | broadcast title |
description | string | broadcast description |
locale | string | broadcast language locale, e.g. en |
availableForReplay | boolean | true = keep the broadcast available as a replay |
isLocked | boolean | true = lock the broadcast (X's isLocked flag) |
chatOption | string | X chat permission option (numeric string) |
thumbnailMediaId | string | pre-live slate media id |
skipPost | boolean | publish: true skips the post X would make announcing it |
confirm | boolean | true only after the user approved this exact public or irreversible action |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
manage_x_stream_source#
Manage X stream sources destructive
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 sourceId to manage_x_broadcast. The stream key is a secret, so never post it. 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 credits (X bills per call).
| Parameter | Type | Description |
|---|
action | "list" | "get" | "create" | "rename" | "delete" | "region" | default 'list' |
id | string | source id, for get, rename and delete |
name | string | label, for create and rename |
region | string | create only: 'atla' or 'pdxa', or an AWS region X maps onto one |
confirm | boolean | true only after the user approved deleting this source |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
read_x_broadcast_chat#
Read an X broadcast chat read-only
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 credits (X bills per call).
| Parameter | Type | Description |
|---|
broadcastIdrequired | string | broadcast id from list_x_broadcasts |
maxResults | number | 1-200, default 100 |
paginationToken | string | next page token from the previous reply |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
send_x_broadcast_chat#
Send a message to an X broadcast chat
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 then must be reconnected once under Settings > Connectors > X (the tool says so without calling X). Costs credits (X bills per call).
| Parameter | Type | Description |
|---|
broadcastIdrequired | string | running broadcast id from list_x_broadcasts |
textrequired | string | chat message, max 140 characters; a longer one is refused |
replyTo | string | chat message id to reply to |
confirm | boolean | true only after the user approved this exact message; it is posted publicly |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
moderate_x_broadcast#
Moderate an X broadcast chat destructive
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 username, every chat of the brand; when the brand moderates another host, broadcastId scopes it to that host), and the account's PERSISTENT chat moderators, which apply to every current and future broadcast: 'list_moderators', 'add_moderator', 'remove_moderator' (userId or username). Only the host or a moderator can moderate, and X says so if not. A username costs one extra lookup. 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 credits (X bills per call).
| Parameter | Type | Description |
|---|
actionrequired | "remove_message" | "mute" | "unmute" | "list_moderators" | "add_moderator" | "remove_moderator" | "list_mutes" | "mute_everywhere" | "unmute_everywhere" | remove_message, mute, unmute, list_moderators, add_moderator, remove_moderator, list_mutes, mute_everywhere or unmute_everywhere |
broadcastId | string | broadcast id; for remove_message, mute and unmute |
messageId | string | chat message id to remove; with mute, also removes that message |
userId | string | numeric X account id |
username | string | X handle, resolved to the id for you |
until | string | mute end, ISO 8601 or epoch ms |
confirm | boolean | required for remove_message |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
x_chat_settings#
Read the X Chat privacy settings read-only
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-message access: an X connection made before 2026-08-25 must be reconnected once under Workspace > Connectors > X (the tool says so without calling X). Costs credits (X bills per call).
| Parameter | Type | Description |
|---|
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
update_x_chat_settings#
Change the X Chat privacy settings destructive
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 merged onto the stored ones (X does not promise a partial call setting keeps the rest). Returns the settings X stored, not what was sent. Reversible. Needs X connected with direct-message access: an X connection made before 2026-08-25 must be reconnected once under Workspace > Connectors > X (the tool says so without calling X). Costs credits (X bills per call).
| Parameter | Type | Description |
|---|
allowDmsFrom | "all" | "verified" | "following" | who may message the brand: all, verified or following |
alwaysAllowSubscribers | boolean | subscribers can always message the brand, whatever allowDmsFrom says |
qualityFilter | boolean | true = low-quality message requests go to a separate folder |
readReceipts | boolean | send and receive read receipts |
callsEnabled | boolean | audio and video calls on or off |
enhancedCallPrivacy | boolean | true = enhanced privacy for calls |
acceptCallsFrom | object | who may call the brand: flags for each group below |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
list_x_chats#
List X Chat conversations read-only
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 short or empty while more remain: keep paging with the same filters. view 'admin_requests' (id = a group the brand admins) lists who asked to become an admin; view 'invite' (inviteLink, or id + inviteToken) previews an invite link: group name, member count, whether the brand is in it. view 'message_requests' lists the MESSAGE REQUESTS waiting outside the inbox (inbox 'priority', the default, or 'low_quality' for X's filtered tab): who sent each one (@handle, name, followers, verified), which accounts the brand follows also follow them, and whether they changed their name or picture recently (X's impersonation hint). A message sent in X Chat is encrypted and cannot be read; one sent as a plain direct message can, with list_x_dms participantId = the sender. Show that to the user before manage_x_chat accept_request. withMembers:true resolves member @handles, and each user X returns is billed. Read-only. Billed per conversation or request returned, plus one user lookup per request sender, so keep maxResults small. Needs X connected with direct-message access: an X connection made before 2026-08-25 must be reconnected once under Workspace > Connectors > X (the tool says so without calling X). Costs credits (X bills per call).
| Parameter | Type | Description |
|---|
view | "conversations" | "admin_requests" | "invite" | "message_requests" | default 'conversations' |
inbox | "priority" | "low_quality" | message_requests: X's Priority tab (default) or the filtered low-quality one |
withSenders | boolean | message_requests: resolve who sent each request (default true, each sender billed) |
id | string | one conversation; for admin_requests, the group (g + digits) |
maxResults | number | 1-100 (message_requests 1-50), default 10; every conversation or request returned is billed |
paginationToken | string | nextToken from a previous call |
withMembers | boolean | also resolve member/admin @handles (each one billed) |
unreadOnly | boolean | only unread conversations |
type | "direct" | "group" | only 1:1 or only group conversations |
isArchived | boolean | true only archived, false only not archived |
isMuted | boolean | true only muted, false only not muted |
participantIds | string | 1-10 numeric X account ids, comma-separated, all in the chat |
participantQuery | string | name or @username prefix, 1-50 characters |
startTime | string | latest message at or after this ISO 8601 time |
endTime | string | latest message before this ISO 8601 time |
inviteLink | string | an x.com/i/chat/group_join/... link, for view invite |
inviteToken | string | the 10-character token, with id, for view invite |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
manage_x_chat#
Manage an X Chat conversation destructive
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 (id starts with g; the brand must be an admin): 'admin_settings' (adminOnly {addMember, editGroupInfo, sendMessage, startCall, editMessageTtl, blockScreenCapture}; sendMessage:true makes it announcement-only), 'add_admins' / 'remove_admins' (userIds), 'remove_members' (userIds, confirm:true), 'leave' (confirm:true), 'approve_admin_request' / 'reject_admin_request' (userId), 'reject_join_requests' (userIds; APPROVING a join re-keys the encrypted group, so it is done in the X app), 'enable_invite' (optional title, avatarUrl, welcomeMessage, expiresAt; X stores that preview UNENCRYPTED and shows it to anyone with the link), 'disable_invite' (the shared link dies for good: confirm:true). INBOX, for the brand only: 'archive' / 'unarchive', 'mark_read' (may send the other person a read receipt). MESSAGE REQUESTS: 'accept_request' (id = the sender's numeric id, or the request's conversationId from list_x_chats view message_requests) moves the conversation into the inbox; show the user who sent it first (list_x_dms with participantId reads the message only if it was not sent encrypted). Confirm the exact conversation with the user before passing confirm:true; a refusal is free. The answer is X's own yes or no, never an echo. Needs X connected with direct-message access: an X connection made before 2026-08-25 must be reconnected once under Workspace > Connectors > X (the tool says so without calling X). Costs credits (X bills per call).
| Parameter | Type | Description |
|---|
actionrequired | "mute" | "unmute" | "delete" | "set_disappearing" | "clear_disappearing" | "screen_capture" | "admin_settings" | "add_admins" | "remove_admins" | "remove_members" | "leave" | "approve_admin_request" | "reject_admin_request" | "reject_join_requests" | "enable_invite" | "disable_invite" | "accept_request" | "archive" | "unarchive" | "mark_read" | conversation action; the tool description lists each one and what it needs |
idrequired | string | 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 | string[] | NUMERIC X account ids (add_admins, remove_admins, remove_members, reject_join_requests) |
userId | string | NUMERIC X account id (approve_admin_request, reject_admin_request) |
disappearAfter | string | set_disappearing: '24h', '7d', '90m' or a number of seconds |
blockScreenshots | boolean | screen_capture: true = block screenshots of the chat |
notifyOnScreenshot | boolean | screen_capture: true = notify everyone when someone takes a screenshot |
adminOnly | object | admin_settings: true = only admins may |
title | string | enable_invite: the group name shown on the link preview (stored unencrypted) |
avatarUrl | string | enable_invite: https image for the preview |
welcomeMessage | string | enable_invite: welcome text on the invite preview; stored unencrypted |
expiresAt | string | enable_invite: ISO 8601 or epoch ms; omit for a link that does not expire |
confirm | boolean | true only after the user approved this exact delete / leave / removal / link shutdown |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
x_user#
Look up any public X account read-only
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 a counter X does not return is reported as unknown rather than zero. Needs X connected.
| Parameter | Type | Description |
|---|
usernamerequired | string | the handle, with or without the @ |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
x_account#
Read the connected X account read-only
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 counter X does not return comes back under `absent` with the reason and is NEVER reported as zero. Read-only. Rides the account lookup Hermoso already makes when it posts — X bills per request and not per field — so it costs at most one User: Read and is free on a cached lookup. Needs X connected (Settings > Connectors > X).
| Parameter | Type | Description |
|---|
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
edit_telegram_message#
Edit a Telegram post destructive
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 wording is not kept. Free, 0 credits. Needs Telegram connected.
| Parameter | Type | Description |
|---|
brand | string | 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 CALL ONLY. A name that matches no profile, or two, is REFUSED and nothing is done. |
chatIdrequired | string | the channel’s @username or numeric chat id |
messageIdrequired | number | from post_to_telegram, or the number at the end of a t.me link |
text | string | the new text, for a post with no media |
caption | string | the new caption, for a post carrying a photo/video/album |
linkPreview | boolean | false to suppress the link preview |
parseMode | "HTML" | 'HTML' to format the new text with Telegram's HTML tags; omit for plain text |
buttons | object | object[][] | 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 | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
pin_telegram_message#
Pin or unpin a Telegram message
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 administrator with pin rights. Free, 0 credits. Needs Telegram connected.
| Parameter | Type | Description |
|---|
brand | string | 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 CALL ONLY. A name that matches no profile, or two, is REFUSED and nothing is done. |
chatIdrequired | string | the channel’s @username or numeric chat id |
messageId | number | required to PIN; optional when unpinning |
unpin | boolean | true to unpin instead of pin |
notify | boolean | true to notify members; default silent |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
create_telegram_invite_link#
Create a trackable Telegram invite link
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 member limit and join approval are mutually exclusive in Telegram’s own docs and passing both is refused by name. The bot must be an administrator with invite rights. Free, 0 credits. Needs Telegram connected.
| Parameter | Type | Description |
|---|
chatIdrequired | string | the channel’s @username or numeric chat id |
name | string | what this link is for — the campaign name; up to 32 characters, and the whole point of the tool |
expiresAt | string | ISO 8601 timestamp after which the link stops working |
memberLimit | number | how many people may join through it; cannot be combined with requireApproval |
requireApproval | boolean | each join waits for an admin; cannot be combined with memberLimit |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
create_linkedin_event#
Create a LinkedIn event
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). Optionally attach a LinkedIn REGISTRATION FORM (registrationForm:true + privacyPolicyUrl, or your own approved leadFormId); sign-ups arrive as EVENT leads in list_linkedin_leads(leadType:"EVENT"). Read back from LinkedIn. Warning: ANNOUNCING IS A SEPARATE, PUBLIC STEP (announce:true does it in this call): LinkedIn requires a SEPARATE post to make an event publicly visible, so creating one puts nothing in anyone’s feed and the brand decides when to tell people. That separation is LinkedIn’s own and it is why an agent cannot accidentally broadcast to a company’s followers. Edit it with update_linkedin_event until it starts. A custom background image is not offered yet. Needs the Events permission (administrator flag) and a reconnect, the same two conditions as list_linkedin_events. 0 credits.
| Parameter | Type | Description |
|---|
namerequired | string | the event title |
startsAtrequired | string | ISO 8601, must be in the future — LinkedIn rejects an event that has already begun |
endsAt | string | ISO 8601, after startsAt; defaults to one hour after the start |
description | string | event description shown on the event page |
organizationId | string | the Page — omit if only one is shared with this profile |
format | "external" | "in_person" | "live" | inferred from url / address when omitted; live needs LinkedIn Live access |
url | string | external: where it streams or where people join; in_person: an info page |
address | object | in_person venue |
venueDetails | string | room, floor, parking… |
registrationForm | boolean | attach a LinkedIn-built registration form (needs privacyPolicyUrl) |
privacyPolicyUrl | string | privacy policy link; required with registrationForm:true |
leadFormId | string | or attach your own APPROVED lead form instead of registrationForm |
discoveryMode | "LISTED" | "URL_ONLY" | LISTED (shown in event search) or URL_ONLY (reachable by link only) |
announce | boolean | 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 — show the user the copy and get a yes first |
announceText | string | the copy of the announcing post |
update_linkedin_event#
Edit a LinkedIn event destructive
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 | Description |
|---|
eventIdrequired | string | from list_linkedin_events or create_linkedin_event |
organizationId | string | Page id that organizes the event; omit when only one is shared |
name | string | new event title |
description | string | new event description |
startsAt | string | ISO 8601 |
endsAt | string | ISO 8601 |
url | string | join or info link for an online or external event |
address | object | in-person venue address: {line1, city, region, postalCode, country} |
venueDetails | string | room, floor or parking notes for an in-person event |
discoveryMode | "LISTED" | "URL_ONLY" | LISTED or URL_ONLY (see create_linkedin_event) |
list_linkedin_events#
List a LinkedIn Page’s events read-only
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 SHARED with this profile are readable. Warning: Needs the Events permission: an administrator switches it on for the deployment, and because LinkedIn grants permissions at authorise time and never retroactively, every existing LinkedIn connection must RECONNECT once before it works. The tool tells you which of those two applies rather than failing generically. Read-only, 0 credits. Needs LinkedIn connected.
| Parameter | Type | Description |
|---|
organizationId | string | the Page to read — omit if only one Page is shared with this profile; list_linkedin_pages shows them |
start | number | pagination offset; default 0 |
count | number | max 50, default 10 |
search_linkedin_ad_library#
Search LinkedIn’s official Ad Library read-only
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 advertising policy that was violated and how it was caught. That last one is the cheapest way to learn what not to write. Coverage is global. COVERAGE IS BOUNDED and the reply always says so: nothing created before 2023-06-01, and an ad is dropped one year after its last impression, so an empty result usually means the window rather than the market. 25 ads per page. Needs LinkedIn connected — no extra permission, any LinkedIn connection works. 0 credits.
| Parameter | Type | Description |
|---|
keyword | string | searched in the ad’s content; several words are a logical AND, not a phrase |
advertiser | string | advertiser name, e.g. Notion |
payerName | string | who PAID for the ad, which is often a different entity from the advertiser |
countries | string | comma-separated ISO codes the ad was served in, e.g. US,GB |
dateFrom | string | YYYY-MM-DD, inclusive — must be given together with dateTo |
dateTo | string | YYYY-MM-DD, EXCLUSIVE — must be given together with dateFrom |
includeFacets | string | only ads whose targeting INCLUDES these facet categories: LANGUAGE, LOCATION, AUDIENCE, AGE, GENDER, COMPANY, EDUCATION, JOB, INTERESTS, TRAITS |
excludeFacets | string | only ads whose targeting EXCLUDES these facet categories |
impressionsFrom | number | lower bound of the estimated impressions range; pass both bounds or neither |
impressionsTo | number | upper bound; pass both bounds or neither |
sort | "DESCENDING" | "ASCENDING" | by creation time; default DESCENDING (newest first) |
start | number | pagination offset; default 0 |
count | number | max 25 |
search_linkedin_brand_partnerships#
Find sponsored creator posts on LinkedIn read-only
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. This is a different dataset from search_linkedin_ad_library and from search_linkedin_ads: those are paid placements, these are disclosed endorsements in the feed. DIFFERENT CONTRACT from the ad search, so do not carry parameters across: `keyword` is REQUIRED here, `dateRange` is an ENUM (PAST_24H / PAST_WEEK / PAST_MONTH) rather than a date range, and the reply is POST URLS ONLY — no advertiser, no targeting, no impressions. Posts are queryable for one year after publication. 25 per page. Needs LinkedIn connected, no extra permission. 0 credits.
| Parameter | Type | Description |
|---|
keywordrequired | string | REQUIRED on this finder, unlike the ad search — searched in the post content; several words are a logical AND |
dateRange | "PAST_24H" | "PAST_WEEK" | "PAST_MONTH" | an ENUM, not a date range; omit to search everything LinkedIn still holds |
start | number | pagination offset; default 0 |
count | number | max 25 |
list_business_categories#
Google Business categories for a country read-only
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 Google’s list genuinely differs by country — there is no worldwide answer. view:"FULL" additionally returns the service types and the attributes valid for each category, which is how you join this to list_business_attributes. Read-only, 0 credits. Needs Google Business Profile connected.
| Parameter | Type | Description |
|---|
regionCoderequired | string | ISO 3166-1 alpha-2 country code — US, GB, CA, AU |
languageCode | string | BCP 47, default en |
view | "BASIC" | "FULL" | FULL adds service types and valid attributes |
filter | string | search the display names, e.g. “coffee” |
pageSize | number | categories per page |
pageToken | string | next page token from the previous reply |
list_business_attributes#
Google Business attributes available for a listing read-only
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_categories) for a category, or regionCode for a country. Passing more than one is refused by name, because Google silently ignores the ones it does not use and would answer about a different scope than you asked for. Read-only, 0 credits. Needs Google Business Profile connected.
| Parameter | Type | Description |
|---|
locationName | string | locations/{id} — from list_business_locations |
categoryName | string | categories/gcid:… — from list_business_categories |
regionCode | string | ISO 3166-1 alpha-2 country code |
languageCode | string | BCP 47 language code; default en |
showAll | boolean | true = send Google's showAll flag for the full attribute list |
pageSize | number | attributes per page; default 200 |
pageToken | string | next page token from the previous reply |
business_google_updated#
Edits Google is proposing to a listing read-only
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 failure. Read-only — accepting or rejecting an edit is a write this tool does not do. 0 credits. Needs Google Business Profile connected.
| Parameter | Type | Description |
|---|
locationNamerequired | string | locations/{id} — from list_business_locations |
telegram_account#
Read a Telegram channel or group read-only
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 bot is in, so there is no default and guessing would report another channel’s numbers as this one’s; list_telegram_chats reports any chat that messaged the bot in the last 24 hours. Warning: TELEGRAM HAS NO PER-POST ANALYTICS AT ANY PERMISSION LEVEL: its Message object carries no `views` field and the Bot API has no history method, so post views and a post list are genuinely unavailable to a bot — both come back under `absent` with the reason, and neither is ever reported as zero. The bot must be a member of the chat. Read-only, 0 credits. Needs Telegram connected.
| Parameter | Type | Description |
|---|
chatIdrequired | string | the channel’s @username or numeric chat id — required; there is no default |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
bluesky_account#
Read a Bluesky account read-only
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 read: `self` is true only for the connected account, compared on the DID because a Bluesky handle can change. A counter Bluesky does not return comes back under `absent` with the reason and is NEVER reported as zero — all three are optional in the lexicon. Read-only, 0 credits, no extra permission. Needs Bluesky connected (Settings > Connectors > Bluesky, or connect_connector).
| Parameter | Type | Description |
|---|
actor | string | a Bluesky handle (hermoso.ai or @hermoso.ai) or a did:plc:… — omit it to read the connected account |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
bluesky_post_metrics#
Read likes, reposts, replies and quotes on your Bluesky posts read-only
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. A uri Bluesky returns nothing for is reported as MISSING (deleted, or not on the connected account), never as zero engagement. Read-only, 0 credits. Needs Bluesky connected.
| Parameter | Type | Description |
|---|
brand | string | 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 CALL ONLY. A name that matches no profile, or two, is REFUSED and nothing is done. |
urisrequired | string[] | AT-URIs of the posts, at most 25 (Bluesky’s own maximum for one call) |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
list_bluesky_convos#
List Bluesky direct-message conversations read-only
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 access ticked — and says so precisely if the saved one cannot chat, which is a property of the password and NOT a broken connection. Filters: readState "unread", status "request" or "accepted", kind "direct" or "group". An unknown filter value is refused by name, never silently dropped.
| Parameter | Type | Description |
|---|
limit | number | how many conversations, 1–100 (default 25) |
cursor | string | walk further back — pass the cursor from a previous call |
readState | "unread" | only conversations with unread messages |
status | "request" | "accepted" | "request" = from someone this account does not follow |
kind | "direct" | "group" | direct or group conversations only |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
read_bluesky_dm#
Read the messages in one Bluesky conversation read-only
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 | Description |
|---|
convoIdrequired | string | from list_bluesky_convos |
limit | number | how many messages, 1–100 (default 30) |
cursor | string | walk further back through older messages |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
send_bluesky_dm#
Send a Bluesky direct message
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, only accepts DMs from people they follow, has blocked this account, is suspended, or does not exist. There is deliberately no broadcast form — a recipient is always named.
| Parameter | Type | Description |
|---|
convoId | string | reply into an existing conversation (from list_bluesky_convos). Omit to start one with `to`. |
to | string | the recipient’s Bluesky handle, e.g. alice.bsky.social. Ignored when convoId is given. |
textrequired | string | the message, up to 1000 characters |
replyToMessageId | string | reply to a specific message in the conversation |
dryRun | boolean | 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 it follows, has blocked this one, is suspended, or does not exist. Free, and the message is NOT sent. Use it before writing a long DM to someone who has never been messaged, so a refusal costs nothing instead of arriving after the words were written. `text` is still validated for length so a dry run cannot pass on a message the real send would refuse. |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
mark_bluesky_convo_read#
Mark Bluesky conversations as read
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 the one requested. Free.
| Parameter | Type | Description |
|---|
convoId | string | from list_bluesky_convos. OMIT to mark EVERY conversation on the account read. |
messageId | string | mark read only up to this message; omit to clear the whole conversation |
status | "request" | "accepted" | 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 when you asked to clear only the requests. |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
manage_bluesky_convo#
Accept, mute, lock, leave or prune a Bluesky DM conversation destructive
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 still sees the message, because Bluesky offers no delete-for-everyone in chat — and leaving a conversation cannot be undone from here. Accepting a request that was already accepted is reported as such rather than as a change, because Bluesky says so by returning no revision. Free, and it needs the same PRIVILEGED app password as every other Bluesky DM tool. Get a convoId from list_bluesky_convos.
| Parameter | Type | Description |
|---|
actionrequired | "accept" | "mute" | "unmute" | "lock" | "unlock" | "leave" | "deleteMessage" | what to do to the conversation |
convoIdrequired | string | from list_bluesky_convos |
messageId | string | required for deleteMessage — from read_bluesky_dm |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
react_to_bluesky_dm#
React to a Bluesky direct message
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 ReactionInvalidValue). Reports the reactions Bluesky reads the message back carrying, not the one requested. Free.
| Parameter | Type | Description |
|---|
convoIdrequired | string | from list_bluesky_convos |
messageIdrequired | string | from read_bluesky_dm |
valuerequired | string | exactly one emoji character, e.g. the thumbs-up emoji |
remove | boolean | true to take the reaction off instead of putting it on |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
tiktok_creator_info#
Read the connected TikTok creator’s posting options read-only
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 pick, then call post_to_tiktok with destination:"post". The SAME privacy options govern PHOTO posts (slideshows), not just video — TikTok takes the same four levels on both. Needs TikTok connected (Settings > Connectors > TikTok).
| Parameter | Type | Description |
|---|
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
post_to_tiktok#
Post a video or photo post to TikTok destructive
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 should appear, and optionally coverIndex. Pass videoUrl for a video. Never pass both — TikTok has no mixed post. TWO destinations. destination:"post" (THE DEFAULT) publishes it LIVE on their profile: TikTok requires the user to CHOOSE the privacy themselves (no default is allowed), so call tiktok_creator_info, show them their real privacy options, and get their choice and an explicit yes before calling. destination:"draft" is ONLY for when the user asks for a draft, or wants to add a TikTok sound or trending audio (TikTok’s API takes no sound for a VIDEO): BEFORE sending, tell them plainly it lands in their TikTok inbox as a DRAFT, that they add the sound in TikTok’s editor, and that THEY must publish it from the TikTok app — nothing goes live until they do. Never pick draft on your own. A photo post published with destination:"post" gets a TikTok-recommended track automatically (autoAddMusic, default on) that they can change in the app. Pass Hermoso render URLs (or upload_file urls for local/external files). Needs TikTok connected (Settings > Connectors > TikTok).
| Parameter | Type | Description |
|---|
account | string | 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 refused by name, never guessed); omit when there is one. |
brand | string | 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 REFUSED and nothing is posted. |
hook | string | WHAT ANGLE this post is built on, recorded only at publish time. post_performance ranks hooks on it (a winner needs 5 posts sharing ONE hook), so pass a list_hooks id (e.g. "direct_callout", "before_after") or reuse your own wording EXACTLY across a campaign. Omit it and this post never votes on which hook works. |
subject | string | 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 | the post's FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format |
ideaId | string | short id of the content-plan idea this post came from |
videoUrl | string | the video to post — a Hermoso render URL or an upload_file url. Omit for a photo post. |
imageUrls | string[] | a PHOTO POST: 1–35 image URLs in slide order. One url = a single-image photo post. Do not combine with videoUrl. |
coverIndex | number | photo posts: which slide is the cover, 0-based. Default 0 (the first slide). |
destination | "post" | "draft" | "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 draft or want to add a TikTok sound, and only after telling them so. |
title | string | the caption — hashtags go here (video ≤2200 chars, photo post ≤4000) |
photoTitle | string | photo posts only: a short title above the caption (≤90 chars). Defaults to the caption’s first line. |
privacy | "PUBLIC_TO_EVERYONE" | "MUTUAL_FOLLOW_FRIENDS" | "FOLLOWER_OF_CREATOR" | "SELF_ONLY" | 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 | true = turn comments off on this post |
disableDuet | boolean | video only — TikTok has no duet on a photo post |
disableStitch | boolean | video only — TikTok has no stitch on a photo post |
autoAddMusic | boolean | photo posts only: let TikTok add a recommended track (default true — a silent slideshow reads as broken) |
coverTimestampMs | number | video only: which frame to use as the cover, in ms |
aiGenerated | boolean | 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’s own footage) is NOT. true/false overrides. |
brandedContent | boolean | discloses a paid partnership — cannot be combined with SELF_ONLY privacy |
yourBrand | boolean | discloses that this promotes the creator’s own brand |
platformCover | boolean | VIDEO COVER. Omit it (the default) and Hermoso sets the video’s best frame — the same frame as its Library thumbnail — as the cover (TikTok video_cover_timestamp_ms, on a direct post — a draft takes no cover, you pick it in the TikTok app). true = send no cover and let the platform pick (usually the first frame). A cover you pass yourself always wins. |
tiktok_post_status#
Check whether a TikTok post finished publishing read-only
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 a failure. Never post the video again while it is processing. Free, read-only. Needs TikTok connected (Settings > Connectors > TikTok).
| Parameter | Type | Description |
|---|
publishIdrequired | string | the publishId post_to_tiktok returned |
account | string | which connected TikTok account the post went out on (@handle or id from list_connector_accounts) when the profile has more than one |
tiktok_account#
Read the connected TikTok account read-only
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 | Description |
|---|
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
list_tiktok_videos#
List the connected account’s TikTok posts read-only
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 does not return comes back under `unresolved` — meaning it is not on this account or no longer exists, which TikTok does not distinguish — never as a zero. Only ever the connected user’s OWN videos. Warning: TIKTOK OFFERS NO WAY TO DELETE OR EDIT A PUBLISHED POST through its API — not the caption, not the privacy level, not the comment/duet/stitch settings, not the cover. Every one of those is fixed at the moment of publishing. If the user wants a TikTok changed or taken down, tell them plainly that it has to be done in the TikTok app; do not look for a tool for it. Read-only, 0 credits. Needs TikTok connected.
| Parameter | Type | Description |
|---|
brand | string | 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 CALL ONLY. A name that matches no profile, or two, is REFUSED and nothing is done. |
limit | number | 1-20, default 10 (ignored when videoIds is given) |
videoIds | string[] | read these specific TikTok video ids instead of listing recent ones — up to 20 per call |
account | string | which connected account: its @handle or id from list_connector_accounts; needed only when several are connected |
upload_meta_asset#
Upload an asset to a Meta ad account
Upload creative(s) — a finished Hermoso ad OR arbitrary user files (e.g. a folder of media from the user’s desktop) — into a connected ad account’s ASSET LIBRARY so the user or a later ad-build step can use them in their OWN campaigns. Pass `url` for one file, or `urls` (up to 20) to BULK-upload in a single call. Each accepts a public https URL, a data: URI, or a Hermoso /generated path; for LOCAL files call upload_file first and pass the url(s) it returns. Image -> image hash; video -> video id. Pass adAccountId from list_meta_pages.
| Parameter | Type | Description |
|---|
adAccountIdrequired | string | ad account id (digits or act_… — from list_meta_pages) |
url | string | a single public https URL / data: URI / /generated path |
urls | string[] | up to 20 media URLs/paths for a one-call BULK upload |
kind | "image" | "video" | inferred from the URL if omitted |
name | string | a label for the asset |
create_meta_campaign#
Create a Meta ad campaign (paused) destructive
Create a campaign on a connected Meta ad account. Always created PAUSED — it spends NOTHING until you activate it with set_meta_campaign_status(confirm:true). Optionally set a dailyBudgetUsd. Pass adAccountId (from list_meta_pages) + an objective. Needs ads-management permission on the connected account. CATALOG / ADVANTAGE+ CATALOG ADS (also called DPA or dynamic product ads) ARE SUPPORTED: pass productCatalogId here and productSetId on the ad set, and the campaign is catalog-backed. Say YES when a user asks for catalog, Advantage+ catalog, DPA or feed-driven ads.
| Parameter | Type | Description |
|---|
namerequired | string | campaign name |
adAccountIdrequired | string | ad account id (digits or act_… — from list_meta_pages) |
objective | "OUTCOME_TRAFFIC" | "OUTCOME_AWARENESS" | "OUTCOME_ENGAGEMENT" | "OUTCOME_LEADS" | "OUTCOME_SALES" | "OUTCOME_APP_PROMOTION" | default OUTCOME_TRAFFIC |
bidStrategy | "LOWEST_COST_WITHOUT_CAP" | "LOWEST_COST_WITH_BID_CAP" | "COST_CAP" | "LOWEST_COST_WITH_MIN_ROAS" | with dailyBudgetUsd: how the campaign bids across its ad sets — LOWEST_COST_WITHOUT_CAP (default: highest volume), LOWEST_COST_WITH_BID_CAP / COST_CAP (each ad set then needs bidAmountUsd), LOWEST_COST_WITH_MIN_ROAS (each ad set needs minRoas) |
dailyBudgetUsd | number | optional campaign daily budget in USD (1–10000); real spend once ACTIVE. With it, the campaign holds the budget (Advantage+ campaign budget) and every ad set under it carries NONE — create_meta_adset / create_meta_ad then send no ad-set budget |
productCatalogId | string | CATALOG (dynamic product) ADS — the PRODUCT CATALOG this campaign advertises, from list_meta_catalogs. This is the CAMPAIGN-level half of a catalog ad; the AD SET takes a productSetId and the creative takes the same set. Passing a product SET id here is refused by name with the level it belongs on. The binding is READ BACK from Meta before it is reported, so a 200 is never narrated as “bound”. |
set_meta_campaign_status#
Activate or pause a Meta campaign destructive
Turn a campaign ON (ACTIVE) or OFF (PAUSED). ACTIVATING STARTS REAL AD SPEND — you MUST first show the user the campaign name + its daily budget, get an explicit yes, then call with status:"ACTIVE" and confirm:true. Pausing is always safe. Needs ads-management permission.
| Parameter | Type | Description |
|---|
campaignIdrequired | string | the campaign id (from create_meta_campaign) |
statusrequired | "ACTIVE" | "PAUSED" | ACTIVE = start spending; PAUSED = stop |
confirm | boolean | REQUIRED true to activate (real spend) — set only after the user explicitly approved the budget |
Mailchimp
list_mailchimp_audiences#
List Mailchimp audiences read-only
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 | Description |
|---|
limit | number | |
offset | number | |
list_mailchimp_segments#
List Mailchimp segments read-only
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 | Description |
|---|
audienceIdrequired | string | |
limit | number | |
offset | number | |
list_mailchimp_campaigns#
List Mailchimp campaigns read-only
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 Mailchimp connected (Settings > Connectors > Mailchimp, or connect_connector with an API key).
| Parameter | Type | Description |
|---|
campaignId | string | |
status | "save" | "paused" | "schedule" | "sending" | "sent" | |
audienceId | string | |
limit | number | |
offset | number | |
list_mailchimp_templates#
List Mailchimp templates read-only
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 | Description |
|---|
limit | number | |
offset | number | |
list_mailchimp_files#
List Mailchimp File Manager images read-only
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 | Description |
|---|
limit | number | |
offset | number | |
upload_mailchimp_image#
Upload an image to Mailchimp
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). create_mailchimp_template uploads its image itself, so call this only to stock the File Manager. Free. Needs Mailchimp connected (Settings > Connectors > Mailchimp, or connect_connector with an API key).
| Parameter | Type | Description |
|---|
imagerequired | string | a Library id (L0), or a Hermoso or upload_file URL |
name | string | |
folderId | number | a File Manager folder id |
create_mailchimp_template#
Create a Mailchimp email template
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 mailing address Mailchimp requires. Or pass html for the user's own markup (that footer is added if it has no *|UNSUB|*). Saves the template only, nothing is sent; returns the templateId for create_mailchimp_campaign. Free. Needs Mailchimp connected (Settings > Connectors > Mailchimp, or connect_connector with an API key).
| Parameter | Type | Description |
|---|
name | string | |
image | string | a Library id, or a Hermoso or upload_file URL |
imageAlt | string | |
headline | string | |
body | string | |
ctaText | string | |
ctaUrl | string | |
accent | string | #RRGGBB |
background | string | #RRGGBB |
html | string | your own full HTML instead of the layout |
update_mailchimp_template#
Update a Mailchimp template
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 | Description |
|---|
templateIdrequired | string | |
htmlrequired | string | |
name | string | |
delete_mailchimp_template#
Delete a Mailchimp template destructive
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 | Description |
|---|
templateIdrequired | string | |
confirm | boolean | true only after the user approved deleting this template |
send_mailchimp_test_email#
Send a Mailchimp test email
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 | Description |
|---|
campaignIdrequired | string | |
recipientsrequired | string[] | up to 5 email addresses |
confirm | boolean | |
create_mailchimp_campaign#
Create a Mailchimp campaign draft
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_campaign, which shows the recipient count first. Free. Needs Mailchimp connected (Settings > Connectors > Mailchimp, or connect_connector with an API key).
| Parameter | Type | Description |
|---|
templateIdrequired | string | |
subjectrequired | string | |
audienceIdrequired | string | |
previewText | string | |
segmentId | string | |
name | string | |
fromName | string | |
replyTo | string | |
update_mailchimp_campaign#
Update a Mailchimp campaign draft
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 | Description |
|---|
campaignIdrequired | string | |
name | string | |
subject | string | |
previewText | string | |
fromName | string | |
replyTo | string | |
templateId | string | |
audienceId | string | |
segmentId | string | |
send_mailchimp_campaign#
Send or schedule a Mailchimp campaign destructive
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 mismatch, a zero, or a campaign Mailchimp says is not ready is refused). sendTime (ISO, in the future, on :00/:15/:30/:45; Mailchimp schedules only on the quarter-hour) schedules instead of sending now; timewarp:true sends at that time in each recipient's own timezone. Free. Needs Mailchimp connected (Settings > Connectors > Mailchimp, or connect_connector with an API key).
| Parameter | Type | Description |
|---|
campaignIdrequired | string | |
sendTime | string | ISO date-time on the quarter-hour; omit to send now |
timewarp | boolean | |
confirmRecipients | number | the recipient count the dry run showed the user |
confirm | boolean | true only after the user approved this exact send |
cancel_mailchimp_campaign#
Stop a Mailchimp campaign
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 | Description |
|---|
campaignIdrequired | string | |
action | "unschedule" | "cancel" | |
confirm | boolean | |
delete_mailchimp_campaign#
Delete a Mailchimp campaign destructive
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 | Description |
|---|
campaignIdrequired | string | |
confirm | boolean | true only after the user approved deleting this campaign |
mailchimp_report#
Mailchimp campaign performance read-only
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 connected (Settings > Connectors > Mailchimp, or connect_connector with an API key).
| Parameter | Type | Description |
|---|
campaignId | string | |
days | number | 1-3650, default 30 |
limit | number | |
offset | number | |
sync_leads_to_mailchimp#
Sync lead-form leads to a Mailchimp audience
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. An existing contact's status is never changed, and synced leads are remembered per audience so a re-run never repeats a write. since (default 7 days ago), platforms narrows it, dryRun:true lists what would be synced. Free. Needs Mailchimp connected (Settings > Connectors > Mailchimp, or connect_connector with an API key).
| Parameter | Type | Description |
|---|
audienceIdrequired | string | |
consented | boolean | |
since | string | ISO date; default 7 days ago |
platforms | "meta" | "linkedin" | "tiktok" | "x" | "snapchat" | "chatgpt" | "reddit"[] | |
dryRun | boolean | |
Klaviyo
list_klaviyo_lists#
List Klaviyo lists read-only
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 > Klaviyo, or connect_connector with a private API key).
| Parameter | Type | Description |
|---|
name | string | an exact list name |
limit | number | |
cursor | string | nextCursor from a previous call |
list_klaviyo_segments#
List Klaviyo segments read-only
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 | Description |
|---|
name | string | an exact segment name |
limit | number | |
cursor | string | nextCursor from a previous call |
list_klaviyo_campaigns#
List Klaviyo campaigns read-only
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 Klaviyo connected (Settings > Connectors > Klaviyo, or connect_connector with a private API key).
| Parameter | Type | Description |
|---|
campaignId | string | read one campaign in full |
name | string | part of a campaign name |
status | "draft" | "scheduled" | "sending" | "sent" | "cancelled" | |
archived | boolean | |
limit | number | |
cursor | string | nextCursor from a previous call |
list_klaviyo_flows#
List Klaviyo flows read-only
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 private API key).
| Parameter | Type | Description |
|---|
status | string | live, manual or draft |
name | string | part of a flow name |
limit | number | |
cursor | string | nextCursor from a previous call |
list_klaviyo_templates#
List Klaviyo templates read-only
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 | Description |
|---|
name | string | part of a template name |
limit | number | |
cursor | string | nextCursor from a previous call |
list_klaviyo_images#
List Klaviyo images read-only
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 | Description |
|---|
name | string | part of an image name |
limit | number | |
cursor | string | nextCursor from a previous call |
upload_klaviyo_image#
Upload an image to Klaviyo
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 (id and hosted URL). create_klaviyo_template uploads its image itself, so call this only to stock the library. Free. Needs Klaviyo connected (Settings > Connectors > Klaviyo, or connect_connector with a private API key).
| Parameter | Type | Description |
|---|
imagerequired | string | a Library id (L0), a Hermoso or upload_file URL, or a public https image URL |
name | string | |
hidden | boolean | keep it out of the visible asset library |
create_klaviyo_template#
Create a Klaviyo email template
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 sender address Klaviyo requires. Or pass html for the user's own markup (that footer is added if it has no unsubscribe link). Saves the template only, nothing is sent; returns the templateId for create_klaviyo_campaign. Free. Needs Klaviyo connected (Settings > Connectors > Klaviyo, or connect_connector with a private API key).
| Parameter | Type | Description |
|---|
name | string | |
image | string | a Library id, Hermoso or upload_file URL, or a public image URL |
imageAlt | string | |
headline | string | |
body | string | |
ctaText | string | |
ctaUrl | string | |
accent | string | #RRGGBB |
background | string | #RRGGBB |
html | string | your own full HTML instead of the layout |
text | string | plain-text version |
update_klaviyo_template#
Update a Klaviyo template destructive
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 | Description |
|---|
templateIdrequired | string | |
name | string | |
html | string | |
text | string | |
delete_klaviyo_template#
Delete a Klaviyo template destructive
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 | Description |
|---|
templateIdrequired | string | |
confirm | boolean | true only after the user approved deleting this template |
send_klaviyo_test_email#
Send a Klaviyo test email
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 | Description |
|---|
templateIdrequired | string | |
recipientsrequired | string[] | up to 5 email addresses |
confirm | boolean | |
create_klaviyo_campaign#
Create a Klaviyo campaign draft
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, which shows the recipient count first. Free. Needs Klaviyo connected (Settings > Connectors > Klaviyo, or connect_connector with a private API key).
| Parameter | Type | Description |
|---|
templateIdrequired | string | |
subjectrequired | string | |
previewText | string | |
listIds | string[] | |
segmentIds | string[] | |
excludeIds | string[] | |
name | string | |
fromEmail | string | |
fromLabel | string | |
replyTo | string | |
update_klaviyo_campaign#
Update a Klaviyo campaign draft destructive
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 | Description |
|---|
campaignIdrequired | string | |
name | string | |
subject | string | |
previewText | string | |
fromEmail | string | |
fromLabel | string | |
replyTo | string | |
templateId | string | |
listIds | string[] | |
segmentIds | string[] | |
excludeIds | string[] | |
send_klaviyo_campaign#
Send or schedule a Klaviyo campaign destructive
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 Klaviyo cannot produce is refused). sendTime (ISO, in the future) schedules instead of sending now; strategy 'local_timezone' sends at that time in each recipient's own timezone. Free. Needs Klaviyo connected (Settings > Connectors > Klaviyo, or connect_connector with a private API key).
| Parameter | Type | Description |
|---|
campaignIdrequired | string | |
sendTime | string | ISO date-time in the future; omit to send now |
strategy | "static" | "local_timezone" | |
confirmRecipients | number | the recipient count the dry run showed the user |
confirm | boolean | true only after the user approved this exact send |
cancel_klaviyo_campaign#
Stop a Klaviyo campaign destructive
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 | Description |
|---|
campaignIdrequired | string | |
action | "revert" | "cancel" | |
confirm | boolean | |
delete_klaviyo_campaign#
Delete a Klaviyo campaign destructive
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 | Description |
|---|
campaignIdrequired | string | |
confirm | boolean | true only after the user approved deleting this campaign |
klaviyo_report#
Klaviyo campaign and flow performance read-only
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_months...) or start + end (at most a year). Conversions and revenue count on conversionMetric (default 'Placed Order'). Narrow with campaignIds / flowIds or channel. Rates are fractions (0.25 = 25%). Klaviyo allows only 2 report queries a minute and 225 a day, so ask once with a wide window; the same question within 10 minutes is answered from the last read unless fresh:true. Read-only, free. Needs Klaviyo connected (Settings > Connectors > Klaviyo, or connect_connector with a private API key).
| Parameter | Type | Description |
|---|
of | "campaigns" | "flows" | |
timeframe | "last_12_months" | "last_30_days" | "last_365_days" | "last_3_months" | "last_7_days" | "last_90_days" | "last_month" | "last_week" | "last_year" | "this_month" | "this_week" | "this_year" | "today" | "yesterday" | |
start | string | |
end | string | |
statistics | string[] | Klaviyo statistic names, e.g. opens_unique, click_rate, conversion_value |
campaignIds | string[] | |
flowIds | string[] | |
channel | "email" | "sms" | "push-notification" | "whatsapp" | |
conversionMetric | string | metric name or id; default 'Placed Order' |
fresh | boolean | |
sync_leads_to_klaviyo#
Sync lead-form leads to a Klaviyo list
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 never repeats a write. since (default 7 days ago), platforms narrows it, dryRun:true lists what would be synced. Needs profiles:write and lists:write on the key. Free. Needs Klaviyo connected (Settings > Connectors > Klaviyo, or connect_connector with a private API key).
| Parameter | Type | Description |
|---|
listIdrequired | string | |
since | string | ISO date; default 7 days ago |
platforms | "meta" | "linkedin" | "tiktok" | "x" | "snapchat" | "chatgpt" | "reddit"[] | |
dryRun | boolean | |