MCP App Store
by KeenEthics
Business & Operations
Hermoso AI icon

Hermoso AI

by Hermoso AI

Overview

Hermoso runs your marketing from ChatGPT. Ask in plain words and it does the work across four areas you can use together or on their own. Research: see the ads competitors are running right now, find a brand's competitors from its website, break down which hooks and themes their longest-running ads use, and mine ad angles from real customer language. Create: make on-brand image and video ads from your saved brand profile (products, logo, colours and voice), remake a video or static ad you like for your brand from its link, and turn one finished ad into headline or hook variations for testing. Finished files land in your Hermoso Library. Publish: schedule posts to the social accounts you connect in your Hermoso workspace, such as Instagram, Facebook, TikTok, YouTube, LinkedIn, X and Threads, and see what is queued and what already went out. Measure: see which posts and hooks perform best on each channel, and get a review of your paid campaigns across the ad accounts you connect (it changes nothing in them), with suggestions on what to scale, pause, fix or test next. Who it is for: founders, marketers, creators and agencies who want their marketing handled from one conversation. Limits: Hermoso needs a Hermoso account, and it only acts on the brands and accounts you connect to it. It never posts without your instruction, and scheduled posts go out on the accounts you choose. Research and creating ads use credits from your Hermoso account. Ad libraries only show what the platforms make public, so some advertisers return few or no ads. Video ads take one to three minutes to render.

Tools

accept_bluesky_convo

ChatGPT
Use this when a Bluesky DM request should move out of the requests folder into the inbox. Get convoId from list_bluesky_convos. Free.

accept_x_chat_request

ChatGPT
Use this when a message request should move into the brand’s X inbox. id is the sender’s numeric id or the request’s conversationId from list_x_chats view message_requests. Show the user who sent it first; the message itself cannot be read here. Needs X connected; X bills per call, so it costs credits.

add_apple_ads_keywords

ChatGPT
Add targeting keywords to an Apple Ads ad group. keywords is [{text, matchType, bid}] — matchType EXACT (default), BROAD, PHRASE or CATEGORY, and bid optionally overrides the ad group default for that one term. Up to 1000 per call and 5000 per ad group. They are sent ONE AT A TIME on purpose: a single term Apple refuses cannot take the rest of the batch down, and the reply names every refusal with Apple’s own reason, then reads the ad group’s keywords back to prove what actually landed. Keyword text and match type are IMMUTABLE — to change either, delete the keyword and add it again. A keyword is created ENABLED because it cannot serve unless its ad group and campaign are enabled too; if the ad group is ALREADY live, adding keywords serves on the next auction and therefore takes confirm:true. Free.

add_apple_ads_negative_keywords

ChatGPT
Add negative keywords to an Apple Ads campaign or ad group — search terms your ads must NOT show for. Pass exactly one of campaignId (campaign-wide) or adGroupId (that ad group only); Apple rejects a request carrying both. keywords is [{text, matchType}] with matchType BROAD (default) or EXACT. Never confirm-gated and never a spend risk, because a negative keyword only ever RESTRICTS where you show. Text and match type are immutable — delete and re-add to change either. Free.

add_bing_webmaster_site

ChatGPT
Add a site to the connected Bing Webmaster account. Important: ADDING IS NOT VERIFYING, and saying so is most of this tool's value: the site arrives UNVERIFIED and every read on it is refused until an ownership proof is placed on the site itself — an XML file at the root, a meta tag in the home page <head>, or a CNAME DNS record — which no API can do. Place one, then call verify_bing_webmaster_site. Microsoft documents that adding a site which is already there does NOT error, so a success here is not even evidence anything changed, which is why the answer is read back from Bing's site list. It is also NOT shared with this profile until the user ticks it under Settings > Connectors > Bing Webmaster Tools > Manage accounts (or call list_connector_accounts with provider bing_webmaster, then set_connector_accounts). 0 credits.

add_google_ads_assets

ChatGPT
Add SITELINKS, CALLOUTS or STRUCTURED SNIPPETS to a Google Ads campaign or ad group — and ATTACH them, which is the part that makes them do anything (an asset sitting in the account library shows nothing at all). Sitelinks are the highest-CTR free win on Search: extra links under the ad, each with its own landing page. Pass assetType plus assets[]: SITELINK needs {linkText (≤25 chars), finalUrl, and optionally description1 / description2}; CALLOUT needs {calloutText (≤25)}; STRUCTURED_SNIPPET needs {header, values[] — at least 3}. Or link assets that already exist with assetResourceNames[]. Assets and links go up in ONE atomic operation, so a rejected link never strands an orphan asset, and the links are READ BACK from Google before you are told they exist. Attaching a live asset to a LIVE (ENABLED) campaign changes what that ad shows on the very next auction — show the user what would appear, get an explicit yes, then pass confirm:true. On a paused campaign it never needs confirmation.

add_google_ads_keywords

ChatGPT
Add keywords — and NEGATIVE keywords — to a Google Ads ad group. A Search ad group with no keywords never shows. Each keyword takes text (≤80 chars, ≤10 words) and matchType EXACT | PHRASE | BROAD (default PHRASE). Set negative:true to BLOCK a term instead of targeting it, which is the cheapest way to stop wasted spend. Read back from Google before you are told they exist. If the parent campaign and ad group are already LIVE, a positive keyword starts bidding real money at once — get an explicit yes and pass confirm:true, or add it with paused:true. Negative keywords only restrict spend and never need confirmation.

add_intercom_note

ChatGPT
Leave an INTERNAL NOTE on an Intercom conversation: only the brand's teammates see it, never the customer (a summary, a suggested reply, a handover). Without confirm:true it adds nothing and returns the preview; call again with confirm:true once the user agrees. Read back from the conversation. Free. Needs Intercom connected (Settings > Connectors > Intercom).

add_microsoft_ads_keywords

ChatGPT
Add keywords to a Microsoft Advertising ad group. Match types are Exact, Phrase and Broad — Microsoft has no broad-match-modifier. Keywords are added Paused unless you set status:"Active"; an Active keyword on a live ad group makes the campaign bid on a new term immediately, so that needs confirm:true. Note that per-keyword bids are honoured but ad-group / keyword BID STRATEGIES are silently ignored by Microsoft — they inherit the campaign’s. Only the keywords Microsoft confirms on the read-back are reported as added.

add_search_console_site

ChatGPT
Add a property to the connected Google account. TWO THINGS THAT MUST REACH THE USER, and the answer states both: (1) ADDING IS NOT VERIFYING — the property arrives with the account as an unverified user and EVERY read on it is refused until ownership is proven with a DNS record, an HTML file or a tag, which no API can do and which the user completes in Search Console itself; (2) the new property is NOT yet shared with this profile, so someone has to tick it under Settings > Connectors > Google Search Console > Manage accounts (or call list_connector_accounts with provider google_search_console, then set_connector_accounts) before any tool here can use it. The permission level is READ BACK from Google, so the answer says which of those two states it is actually in. 0 credits.

add_subtitles

ChatGPT
Burn subtitles into ANY video and get the .srt too; nothing is cut or re-rendered. Set textStyle only when the user describes a look (default: white sentence case, thin outline, bottom). Timed per sentence, or auto:'words' for phrases on their spoken words. Or pass cues to burn your own lines exactly, untranscribed (reel-style phrases, stage directions). burn:false returns only the .srt. Takes a video file or URL, a YouTube/Vimeo-style page, or a TikTok / Reel / X post link. No speech is refused and refunded. Runs in the background and lands in the Library.

add_video_to_youtube_playlist

ChatGPT
Use this when a video should be added to one of the channel’s playlists. Returns the new entry’s itemId. position is zero-based; omit it to append. Free.

add_x_ads_targeting

ChatGPT
Add targeting criteria to an X LINE ITEM. Targeting does NOT attach to a campaign on X — a campaign carries only budget and funding. Pass locationIds resolved with x_ads_geo_search, and/or criteria[] for any of X's other 37 targeting vocabularies when you already hold the ids. X takes ONE criterion per API call, so a set is several calls: each is reported individually, a partial failure NAMES what did not apply, and the line item's FULL targeting is read back afterwards so the answer is what the line item carries rather than what was sent. Adds no spend — the line item stays PAUSED.

add_x_broadcast_chat_moderator

ChatGPT
Use this when someone should moderate the chat on every current and future broadcast of the brand. Pass userId or username. Needs X connected; X bills per call, so it costs credits.

add_x_chat_admins

ChatGPT
Use this when members of a group the brand admins should become admins. userIds are numeric X account ids. Needs X connected; X bills per call, so it costs credits.

add_youtube_watermark

ChatGPT
Use this when the brand wants its small subscribe badge shown on EVERY video on the connected YouTube channel, including later uploads. Square image, at least 150x150, under 10MB. Replaces any watermark already set. YouTube has no read-back, so the reply says accepted, not confirmed. Free.

amplitude_active_users

ChatGPT
Amplitude ACTIVE or NEW user counts over a date range — the top-line "is the product growing" number, and the cheapest Amplitude call to run first after connecting because it proves the credential with no setup. metric is "active" or "new"; interval 1 daily, 7 weekly, 30 monthly. An empty series on a new project is the CORRECT answer and means the connection works and there is no data yet — never report it as a failure. Read-only, 0 credits.

amplitude_chart

ChatGPT
READ BACK A SAVED AMPLITUDE CHART by its id — the chart the user already built in Amplitude's own UI, with whatever segments, filters and date range they configured there. This is Amplitude's equivalent of posthog_insight and mixpanel_insights, and it is the right lane whenever the report already exists: you inherit their definitions instead of rebuilding them from a segmentation query, and it costs one call rather than several. THE CHART ID MUST COME FROM THE USER — Amplitude publishes no endpoint that lists a project's charts — and it is the segment after '/chart/' in the chart's URL (e.g. 'abc123' in https://analytics.amplitude.com/demo/chart/abc123); pasting the whole URL works too. THE RESPONSE SHAPE VARIES BY CHART TYPE, in Amplitude's own words, so read what comes back rather than assuming a series: it may be JSON or a text/CSV body, and BOTH are correct answers rather than one being an error. Read-only, 0 credits.

amplitude_retention

ChatGPT
AMPLITUDE RETENTION — of the people who did startEvent, how many came back and did returnEvent. The two magic values are "_new" (first-time users) and "_active" (any active user), which is what makes "new-user retention" a single call. retentionMode is n-day / bracket / rolling, and "bracket" additionally REQUIRES brackets — it is refused without them rather than silently switched to another mode, because a retention curve computed under a different definition than the one asked for is a wrong answer that looks right. Read-only, 0 credits.

amplitude_segmentation

ChatGPT
AMPLITUDE EVENT SEGMENTATION — an event over time, with filters and group-bys. metric is uniques / totals / pct_dau / average / histogram / sums / value_avg / formula (and "formula" additionally REQUIRES formula, or Amplitude refuses it). Dates take YYYY-MM-DD or YYYYMMDD. AMPLITUDE'S RATE LIMIT IS COST-BASED, not per-request: cost = days × conditions × query type, 1000 per 5-minute window, so one very wide query can exhaust the budget on its own — the fix for a 429 here is to NARROW the range or the conditions, not to retry. AN EMPTY RESULT IS A VALID ANSWER, not a failure: a new or low-traffic Amplitude project genuinely has no data, and reporting that as a broken connection sends the user to fix something that is working. Read-only, 0 credits.

analytics_realtime

ChatGPT
WHO IS ON THE SITE RIGHT NOW — GA4's realtime report, covering roughly the last 30 minutes. Use it to see a launch, a post or a campaign landing in real time. IT IS A DIFFERENT REPORT WITH A DIFFERENT METRIC SET, NOT a shortcut for analytics_report: realtime accepts activeUsers, screenPageViews, conversions and eventCount, and REJECTS sessions outright — asking for sessions here returns an error, not a zero. Realtime dimensions are also narrower (unifiedScreenName, country, deviceCategory, platform, eventName …) and there is no date range at all. The property is the NUMERIC id from list_analytics_properties, never the G-XXXXXXX Measurement ID. Read-only, 0 credits.

analytics_report

ChatGPT
WHAT HAPPENED ON THE SITE — the GA4 Data API report, and the tool that answers "where is our traffic coming from", "which campaign converted", "which landing page is working". Pass metrics by name (activeUsers, sessions, screenPageViews, conversions, totalRevenue, engagementRate, bounceRate, averageSessionDuration …) and dimensions to break them down by (sessionDefaultChannelGroup, sessionSource / sessionMedium / sessionCampaignName, landingPage, pagePath, country, deviceCategory, date, eventName …). GA4 publishes hundreds of both and each property ships its OWN custom dimensions, so names are forwarded as given rather than validated against a copied list — use list_analytics_definitions to see a property's custom ones. THE PROPERTY IS A NUMERIC ID (e.g. 123456789), NOT the G-XXXXXXX Measurement ID — run list_analytics_properties to resolve it. Dates default to the last 28 days and accept either YYYY-MM-DD or GA4's relative forms ("28daysAgo", "yesterday", "today"). Rows come back as flat named objects, and a sampled result says so. Read-only, 0 credits.

analyze_campaigns

ChatGPT
ONE verdict across EVERY ad platform this workspace has connected. It pulls each platform's own report (Meta, Google Ads, ChatGPT Ads, TikTok, LinkedIn, Microsoft Advertising, Pinterest, Reddit, Snapchat, X, Apple Ads), normalises every campaign / ad set / ad into ONE table — spend, impressions, clicks, CTR, CPC, conversions, CPA, ROAS and frequency where the platform reports them — and answers with four lists: scale, pause, fix and test_next, plus insufficient_data. Each recommendation carries the row's OWN numbers as evidence, the reason, a concrete next action, and the EXACT Hermoso tool call that would apply it (for example set_meta_campaign_status with confirm) — which this tool never runs. FOUR REFUSALS TO REPEAT RATHER THAN PAPER OVER: (1) A ROW BELOW THE DATA FLOOR IS NEVER JUDGED — the minimums are stated in the reply, and an under-powered row lands in insufficient_data with what it lacks, never in "pause"; a row whose conversions are too few for a CPA verdict can still be read on CTR and CPC and says so. (2) A PLATFORM WHOSE READ FAILED IS NAMED AS UNREADABLE and its numbers are MISSING, never zero — never move budget on the strength of an absence. (3) A META AD SET IN THE LEARNING PHASE IS HELD BACK, because editing it restarts learning. (4) A CONVERSION COLUMN THE PLATFORM DOES NOT PUBLISH READS UNMEASURED, not zero (Reddit, Snapchat and X in this report). Conversion definitions differ per platform and travel with every row, and each platform's days are its own account's days, so a cross-platform CPA comparison must carry that caveat. Args: days (complete days ending yesterday, default 14, max 90), platforms (omit for every connected one), goal ("lowest CPA", "ROAS", "leads under $30"), level (campaign | ad_group | ad — a platform with no report at that tier is read at campaign level and says so). Changes nothing in any ad account and spends no ad money; it bills one model call over the table.

analyze_video

ChatGPT
A video ad's structure: verbatim transcript (voiceover + on-screen text), beat list, duration and sampled frame times; study a reference before remixing it. ~A transcription call.

answer_google_business_question

ChatGPT
Post the business’s answer to a public question on the brand’s Google Business Profile listing, or delete the answer already there. THIS IS AN UPSERT — one answer per account, so answering again REPLACES the previous one rather than adding a second. Deleting is public and immediate and is confirm-gated. Needs Google Business Profile connected and the project approved.

append_to_doc

ChatGPT
Append text to the end of a Google Doc Hermoso can reach — one it created (pass the documentId from create_doc) or one the user handed over with the Google file picker in the app (find its id with list_drive_files). Optional dropdown:{title, options:[2-50], selected} appends a Google Docs dropdown chip after the text — e.g. text "Status: " + dropdown {title:"Status", options:["Draft","In review","Approved"]} gives a brief a status the team can click to change; the chip is confirmed by reading the doc back. Set it later with update_doc dropdowns.

append_to_sheet

ChatGPT
Append rows to a Google Sheet Hermoso can reach — one it created (pass the spreadsheetId from create_sheet) or one the user handed over with the Google file picker in the app (find its id with list_drive_files). rows = array of row arrays.

apple_ads_change_history

ChatGPT
Who changed what on this Apple Ads account, and when. Call it with start and/or end to get one row per TRANSACTION GROUP — a single user action that may have touched several fields, which is what changeCount counts — with the user, the entity and the event type (CREATE / UPDATE / DELETE). Then pass a row’s detailId back as detailId to get the field-by-field before and after. THE TIME BOUND IS MANDATORY: Apple answers 400 without one, so pass start (meaning "since then"), end ("up to then"), or both. Narrow with campaignId, adGroupId, entityType, eventType or userId. Useful for "why did this campaign’s budget change overnight" and for reconstructing what an agent did on the account. On a CREATE event the old values are empty by design; a DELETE is implemented as a record update so its old values ARE populated. Read-only, free.

apple_ads_impression_share

ChatGPT
COMPETITIVE INTELLIGENCE: what fraction of the available impressions your app actually captures for each search term, by market and day or week. This is the report that finds where you are being out-bid or out-budgeted — a LOW share on a term with high search popularity means budget, bid or eligibility is capping your reach on a term people are really searching, which is the most actionable row Apple publishes. READ THE ENCODING BEFORE QUOTING A NUMBER: lowImpressionShare and highImpressionShare are TIERED, not continuous. Below 91% the two are equal and read as a single percentage; a row where they diverge to 0.91 and 1.0 means ">90%", not "91 to 100". granularity DAILY covers at most 30 days; WEEKLY_SUN_SAT covers at most 4 weeks and its start date MUST be a Sunday (both are refused for free before any request). reportType FIRST_SLOT (default) is the top ad position only; ALL_SLOTS aggregates every position. Timezone is fixed to UTC by Apple. Read-only, free.

apple_ads_keyword_suggestions

ChatGPT
Apple’s OWN search data for an app you advertise — the fastest way to build a real Apple Search Ads keyword list instead of guessing. THE THREE KINDS DO NOT SHARE A FILTER VOCABULARY and Apple 400s any field that is not an attribute of the one you query, so each takes different parameters. kind:"keyword" (the default) returns suggested keyword strings and accepts terms + countriesOrRegions — it has NO queryType, and passing one is refused. kind:"phrase" returns longer search phrases and REQUIRES phrases (the phrases to look up); kind:"category" returns App Store categories and REQUIRES categories. Neither accepts terms. POPULARITY IS RELATIVE, NOT A VOLUME: it ranks terms against each other and cannot be read as searches per month. queryType defaults to "SEARCH", which looks up phrases/categories you name; "SUGGESTION" asks Apple to discover new ones and IS CURRENTLY BROKEN AT APPLE — measured 2026-08-17, it hangs ~34s and returns a server error for a request identical to Apple’s own published example, so retrying will not clear it. REQUIRED: promotedObjectId — the app’s adamId (search_apple_ads_apps with no arguments lists the apps this organization owns) or an Apple Maps brandId. An empty result is about the APP, not the account. Feed the winners straight into add_apple_ads_keywords or bulk_apple_ads_keywords. Read-only, free, spends nothing.

apple_ads_report

ChatGPT
Apple Ads performance — impressions, taps, installs, spend, TTR, CPT, CPA. level is campaign / adgroup / ad / keyword / searchterm; promotedObjectType is apps (App Store, the default) or business-brands (Ads on Apple Maps). startTime and endTime are REQUIRED, as YYYY-MM-DD. campaignId is REQUIRED for every level except campaign — it moved from the URL into a filter, but Apple still enforces it (measured: “campaignId filter is required for AD_GROUP reports when promotedObjectType is APPS”), so only a campaign-level report may be account-wide. adGroupId narrows it further. groupBy is restricted PER LEVEL and an unsupported dimension is refused by name, never dropped: campaign and adgroup take deviceClass/ageRange/gender/countryCode/adminArea/locality/storefront/countryOrRegion, keyword and searchterm take only deviceClass/storefront/countryOrRegion, and ad takes only storefront/countryOrRegion. granularity is optional and carries Apple’s own date rules (HOURLY reaches back 7 days and is unavailable on ad and searchterm reports; DAILY 90 days and needs a range longer than one day; WEEKLY 365 days with an end date at least 14 days ago; MONTHLY needs an end date at least 90 days ago). FOR A SINGLE DAY, OMIT granularity — the totals come back in each row’s totalMetrics. Search-term reports are ORTZ-only. grandTotals adds a summary row; emptyMetrics includes entities with no delivery — App Store reports only, never together with groupBy, and never on a search-term report. A report with NO rows genuinely means there was NO delivery in that window and scope: say exactly that, and never present zeros as measured performance. Read-only and free, so run it first after connecting — it proves the credentials work with zero spend risk.

apple_ads_search_term_popularity

ChatGPT
The most-searched App Store terms in a genre and market — MARKET-WIDE DEMAND, not your own performance, so it works for finding terms to target before you have spent anything. Cross-read it against apple_ads_impression_share: a term ranking high here where your impression share is low is exactly where budget or bid is leaving installs on the table. genre is a FREE-TEXT App Store genre name (e.g. "PRODUCTIVITY_UTILITIES", "TRAVEL") and Apple publishes no fixed enum, so it is forwarded as given rather than validated against a list that would go stale. granularity WEEKLY_SUN_SAT uses fixed Sunday–Saturday weeks generated Mondays at 07:00 UTC with 65 weeks of retention; MONTHLY uses calendar months refreshed on the 5th UTC with 15 months of retention — an empty result for a very recent window usually means Apple has not generated that period yet rather than that there is no demand. NOTE THE GRANULARITY SETS DIFFER between the two insights reports: DAILY exists on impression share and NOT here. Read-only, free.

apple_ads_target_cpa_suggestion

ChatGPT
What Apple thinks an install should cost you. Returns the suggested target CPA for an app — the MAXIMUM tap-install CPI Apple observed across that app’s eligible markets over the last 28 days, together with the market that produced it. Only markets with at least 10 installs in that window qualify, so a brand-new or low-volume app legitimately has no suggestion yet and that is an answer, not a failure. This is the number to put on a MAX_CONVERSIONS campaign, where the campaign-level bid IS the target CPA — it is NOT a per-tap bid and does not belong on a MANUAL_CPT campaign, which refuses a campaign-level bid outright. Scope it with countriesOrRegions to ask about specific markets. Read-only, free, spends nothing.

applovin_ads_pixel

ChatGPT
The AppLovin pixel for the brand’s website: AppLovin’s own install snippet with the account’s Event Key filled in, where it goes (every page’s <head>, Google Tag Manager, or the AppLovin Shopify app), the events to fire for a store or a lead-gen site with copy-ready examples, dedupe ids, and how to validate it. The Event Key is public by design. Calls nothing at AppLovin. Free.

applovin_ads_report

ChatGPT
AppLovin Ads performance: impressions, clicks, CTR, spend, sales, ROAS and checkout revenue by day, campaign, creative set or country. Works with the Reporting Key alone, which every AppLovin account has. Web reports reach back 90 days, app 45; UTC. Read-only, free.

apply_apple_ads_recommendation

ChatGPT
THIS CHANGES WHAT A LIVE CAMPAIGN MAY SPEND, and it is the only tool in the Apple Ads suggestion/recommendation family that touches money. Applying a daily-budget recommendation raises that campaign’s daily budget to Apple’s suggested amount; applying a target-CPA recommendation moves the CPA its auto-bidder chases. REQUIRES confirm:true, and the refusal you get without it is built from the recommendation READ BACK FROM APPLE — it names the campaign, states current -> recommended, and quotes Apple’s own projected spend at the new level — so you are confirming a specific change to a specific campaign rather than confirming that you pressed a button. Pass amount to apply a DIFFERENT number than Apple suggested; omit it to take Apple’s. The campaign’s STATUS IS NOT TOUCHED: this moves a ceiling, it never enables or pauses anything, and it does not make a paused campaign start spending. Only an AVAILABLE recommendation can be applied and applying is TERMINAL. The reply is confirmed by re-reading the CAMPAIGN, not by Apple’s acknowledgement. Free to call; the spending it enables is real.

apply_microsoft_ads_recommendations

ChatGPT
Apply Microsoft Advertising recommendations by id. THIS SPENDS REAL MONEY AND HAS NO UNDO. It raises daily budgets, adds keywords, widens keywords to broad match, DELETES negative keywords so the account buys more searches, and publishes Microsoft-written ads under the brand’s name, all live at the next auction. THE GATE IS UNLIKE THE OTHER MICROSOFT SPEND SWITCHES, AND confirm:true ALONE IS REFUSED: it proves you meant to apply something, not that you know what THESE ones do, and it reads identically for one keyword suggestion and forty budget raises. So call it ONCE with no confirmation. Nothing is applied, and you get every recommendation named with its campaign, exactly what it changes, the current -> recommended budget where there is one, and Microsoft’s own cost estimate. SHOW THAT LIST TO THE USER, get an explicit yes, then call again with confirm:true, confirmCount:<n> and confirmCostIncrease:<n>. BOTH NUMBERS ARE RECOMPUTED FROM A FRESH READ at that moment, so a recommendation Microsoft has withdrawn, already applied or re-priced since you looked fails the check instead of being applied unseen. If ANY id you name is no longer on offer, NOTHING is applied, not even the ones that still are, because applying part of a set you inspected whole is not what you asked for. Microsoft can reject individual items and apply the rest, so the result reports per item: never call a partial apply a failure, and never call it a success. 0 credits to us; the spend lands on the ad account.

apply_tiktok_ads_audience

ChatGPT
Attach a TikTok custom audience to ad groups as an INCLUDE or an EXCLUDE — the call that changes WHO SEES THE ADS, and the reason building audiences is worth doing at all. actionMode is exactly "Apply" or "Disconnect" (TikTok’s enum is case-sensitive), and Apply also needs usageMode "Include" or "Exclude": GETTING usageMode WRONG INVERTS THE ENTIRE AUDIENCE, showing the ads to precisely the people you meant to keep out, and nothing in TikTok’s reply would say so. One audience per call, and the audience and ad groups must be on the same advertiser. A Lookalike cannot be applied to a Reach & Frequency ad group. It spends nothing by itself — every ad group Hermoso builds is born paused — but on an ALREADY-LIVE ad group it changes delivery on the next auction, so name the ad groups to the user first. The ad groups are read back from TikTok.

approve_x_chat_admin_request

ChatGPT
Use this when someone who asked to be an admin of a group the brand admins should be approved. list_x_chats view admin_requests lists the requests. Needs X connected; X bills per call, so it costs credits.

approve_youtube_comment

ChatGPT
Use this when a held or rejected comment on the connected channel should be shown publicly again. Get commentId from list_youtube_comments. Free.

archive_analytics_audience

ChatGPT
ARCHIVE a GA4 audience, and it is ONE-WAY: GA4 publishes no delete and no un-archive anywhere in its API. Any Google Ads remarketing list sourced from it stops refreshing, and because the definition is IMMUTABLE the audience cannot be recovered by editing — rebuilding means a NEW audience that starts collecting members from scratch, so months of accumulated membership are gone. WITHOUT confirm IT ARCHIVES NOTHING and describes the audience read back from Google; check that against what the user asked for, because naming the right audience is the one thing confirm cannot prove. An ambiguous displayName is REFUSED rather than first-matched — GA4 does not enforce unique names and there is no way back. 0 credits.

archive_analytics_custom_dimension

ChatGPT
THE ONLY WAY TO RETIRE A CUSTOM DIMENSION, and it is ONE-WAY. GA4 publishes no delete and no un-archive for custom dimensions anywhere in its API — archiving is permanent through every programmatic surface — so this is how a typo'd or duplicate dimension is cleared, and it also frees the slot it was holding against the 50-event-scoped cap. Reports lose the ability to break down by it. CALLED WITHOUT confirm IT ARCHIVES NOTHING and instead reports what the dimension actually is, read back from Google — check that against what the user asked for before confirming, because naming the right dimension is the only thing confirm cannot prove. Identify it by its parameterName (the event parameter), which list_analytics_definitions lists. Needs edit access on the property.

archive_analytics_custom_metric

ChatGPT
THE ONLY WAY TO RETIRE A GA4 CUSTOM METRIC, and it is ONE-WAY: GA4 publishes no delete and no un-archive for custom metrics anywhere in its API, exactly as for dimensions, so archiving is permanent through every programmatic surface. It frees the slot against the 50-per-property cap, and reports lose the ability to sum or average it. WITHOUT confirm IT ARCHIVES NOTHING and instead describes the metric read back from Google — check that against what the user asked for before confirming, because naming the RIGHT metric is the one thing confirm cannot prove. Identify it by parameterName (from list_analytics_definitions), not the report label. A custom DIMENSION of the same name is a DIFFERENT resource and needs archive_analytics_custom_dimension. Needs edit access. 0 credits.

archive_meta_lead_form

ChatGPT
Retire an INSTANT LEAD FORM so it stops collecting — the only way to tidy a Page’s Instant Forms list, because META DOES NOT SUPPORT DELETING A LEAD FORM AT ALL, only archiving it. This is REVERSIBLE: pass status:"ACTIVE" to switch an archived form back on. Archiving publishes nothing, spends nothing and DELETES NO LEADS — everything the form already collected stays readable with read_meta_leads. CALL IT WITHOUT confirm FIRST: nothing changes and you get the form’s real name, current status and lead count READ BACK FROM META, so you can show the user exactly which form they are about to retire rather than echoing back an id they may have mistyped; then call again with confirm:true. WARNING WORTH RELAYING BEFORE ARCHIVING: if a live ad still points at this form, its button will open nothing — check the ad first. The new status is READ BACK from Meta after the change, so the summary reports what Meta actually stored and says plainly when the change did NOT take; print it verbatim rather than assuming a successful call means a changed form.

archive_openai_ads_audience

ChatGPT
Retire a ChatGPT Ads custom audience. ARCHIVING IS PERMANENT AND THERE IS NO DELETE TO UNDO IT: an archived audience can never be restored, targeted or bid on again, and any campaign that includes or excludes it loses that audience. Without confirm:true this archives NOTHING and instead reports the audience’s real name, status and matched size read back from ChatGPT Ads, so the cost is visible before it is paid. Pass confirm:true only once that is what the user wants.

archive_openai_ads_feed

ChatGPT
Archive a product feed — its products leave every campaign using it. confirm:true required.

archive_openai_ads_lead_form

ChatGPT
Archive a ChatGPT Ads lead form. Not reversible through the API, and refused by ChatGPT Ads while a PUBLISHED Business Agent references it. confirm:true is required; without it nothing changes and the reply says what would.

archive_x_chat

ChatGPT
Use this when a conversation should move out of the brand’s main X inbox. Only the brand’s view changes. Needs X connected; X bills per call, so it costs credits.

associate_linkedin_conversion_campaigns

ChatGPT
Associate LinkedIn campaigns with a conversion rule — or detach them with remove:true. THIS IS WHAT MAKES A CONVERSION COUNT: LinkedIn only attributes a conversion to campaigns associated with its rule, so an unassociated rule reports zero however many events you stream to it. Associate every campaign the conversion could plausibly have come from. It spends nothing, but on a LIVE campaign it changes what the campaign optimises toward and what its report counts. The result is the PER-CAMPAIGN status LinkedIn returned, not a blanket success — repeat any that failed. Free.

authorize_tiktok_ads_spark_post

ChatGPT
Apply an authorization code so this ad account may promote somebody else’s TikTok post. THE CODE CANNOT BE MINTED FROM THE API — only the post’s owner can generate one, in the TikTok app: open the post -> three-dot menu -> Ad settings -> toggle Ad authorization -> choose a duration -> copy the code. Ask them for it; there is no way around this and no endpoint that creates one. Applying a code SPENDS NOTHING and creates nothing that can serve — it only grants permission — so it is deliberately not confirm-gated. If the post is a DUET or STITCH of another post, or mentions one, TikTok requires the ORIGINAL post owner’s code too: pass it as originalPostAuthCode or the call is refused. THE ANSWER IS THE READ-BACK: the reply lists the posts now authorised, because TikTok’s own response body is empty and a bare 200 proves nothing.

backfill_posts

ChatGPT
Import this brand's PAST posts from a channel into the performance record, so 'which hook works' can draw on history rather than only on what was published since Hermoso started recording. Supports facebook, instagram, threads, youtube, tiktok, pinterest and bluesky; the others say plainly why they cannot (LinkedIn and Reddit have no enumerate-my-posts endpoint on our grant, X bills per read so it is excluded from bulk import, Google Business has had no per-post insights since 2023, and Telegram's Bot API cannot read a chat's past messages at all — nothing published before Hermoso is recoverable through a bot token). BOUNDED, RESUMABLE AND QUOTED: it runs as a DRY RUN by default and tells you how many posts it found and what reading them will cost — pass confirm:true to import, and pass the returned cursor to continue. AN IMPORTED POST IS WEAKER EVIDENCE THAN A RECORDED ONE and is labelled 'backfilled': its hook is recovered ONLY where the post matches a Hermoso creation by asset or caption. A post made outside Hermoso stays UNATTRIBUTED — it counts toward channel and format totals but never votes on which hook works. Never guess a hook from a caption. Free.

bind_tiktok_ads_rule

ChatGPT
Bind campaigns, ad groups or ads to an EXISTING automated rule, or unbind them from one. BINDING TO A RULE THAT IS RUNNING AND ABLE TO SPEND puts those objects under standing permission to have their budget or bid raised, which is the same act as arming the rule for them — so it takes the same confirm:true plus confirmScope echo, computed from the rule as TikTok stores it. UNBINDING only ever narrows a rule’s reach and needs neither. TikTok answers a bind with an empty body, so the result is read back off the rule rather than taken from the 200. Free.

bing_webmaster_crawl

ChatGPT
Whether Bing can actually reach the site's pages: crawl statistics (mode "stats"), the URLs with problems (mode "issues", the default), one page's index detail (mode "page", needs url), a directory's (mode "directory"), or the crawl settings themselves (mode "settings"). Issue names are DECODED from Bing's bitmask into readable ones — Code301, Code302, Code4xx, Code5xx, BlockedByRobotsTxt, ContainsMalware, ImportantUrlBlockedByRobotsTxt, DnsErrors, TimeOutErrors — because the raw JSON hands back an integer that means nothing on its own, and a bit Microsoft has added since is reported as unknown rather than dropped. Microsoft notes it may take a few days for a FIXED issue to disappear from this list, so a listed issue is not proof it is still live. Read-only, 0 credits.

bing_webmaster_keywords

ChatGPT
REAL SEARCH DEMAND FOR ANY TERM, free. Modes: "keyword" (impressions for exactly this term over a date range), "related" (the same for terms Bing considers related — the one to use for expanding a seed into a list), "history" (the term's historical series). This is the closest thing in Hermoso to a free keyword planner: google_ads_keyword_ideas needs Google Ads Basic access, and this needs only the Bing connection anyone can set up in two minutes. NOT SITE-SCOPED — these are Microsoft's signatures and they take no site, so it works for ANY term including a competitor's brand, and no property tick list applies. country AND language are BOTH REQUIRED and are deliberately never defaulted: a search volume with no market is unactionable, and picking one for you would answer confidently about the wrong country. Read-only, 0 credits.

bing_webmaster_query_stats

ChatGPT
The Bing twin of search_console_performance, split by what you want pinned. Modes: "queries" (top search terms for the site, the default), "pages" (top pages), "pageQueries" (which queries brought traffic to one page), "queryPages" (which pages one query landed on), "queryPageDetail" (one query on one page, day by day). Worth reading BESIDE Search Console rather than instead of it — the two engines' demand language genuinely differs, and Bing is the one feeding Copilot and ChatGPT. MICROSOFT UPDATES THIS DATA WEEKLY by their own statement, so it lags Search Console and will not move day to day; do not report a flat week as a drop. An unknown mode is refused by name. Read-only, 0 credits.

bing_webmaster_submission_quota

ChatGPT
How many URLs this site may still submit to Bing today and this month, plus the separate content-submission budget. BING SETS THIS PER SITE AND IT VARIES — Microsoft's own documented example allows 5 a day and 24 a month — so it is always read live and never assumed. Check it before promising anyone a bulk submission, because Bing rejects an over-quota batch as a WHOLE rather than taking what fits. Read-only, 0 credits.

bing_webmaster_traffic

ChatGPT
Bing clicks and impressions over time — for the whole site (mode "site", the default), for one page (mode "page", needs url), or for every page under one directory (mode "directory", needs url). Bing is a smaller share of search than Google, but its index is what Copilot and ChatGPT web search read from, so this is the answer-engine visibility read rather than a second traffic report. An unknown mode is refused by name. Read-only, 0 credits.

bluesky_account

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

bluesky_post_metrics

ChatGPT
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.

bulk_apple_ads_keywords

ChatGPT
Create or update up to 1,000 Apple Ads keywords or negative keywords in ONE call — how you seed a whole campaign structure from apple_ads_keyword_suggestions, or re-bid a list after a performance review, instead of one call per keyword. A single request can span DIFFERENT AD GROUPS. op:"create" takes full keyword rows; op:"update" takes ids and can change ONLY bid and status, because a keyword’s text and matchType are immutable after creation — changing either means deleting it and creating a new one. ALLOW-PARTIAL-SUCCESS MATTERS AND APPLE’S DEFAULT IS ALL-OR-NOTHING: with it unset, one bad item (a duplicate text/matchType, say) rejects the ENTIRE batch and nothing is written; pass allowPartialSuccess:true to let the good ones land and read the per-item errors. The reply is the per-item read-back Apple returns — each row carries the stored id, text, matchType and status, so you can see exactly which items landed rather than trusting a 200. Adding keywords into an ENABLED ad group can serve on the next auction and therefore takes confirm:true, the same guard add_apple_ads_keywords uses. Apple caps 1,000 per call and 5,000 per ad group in total. Free.

business_google_updated

ChatGPT
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.

cancel_openai_ads_audience_operation

ChatGPT
Cancel an accepted ADD or REMOVE on a ChatGPT Ads custom audience BEFORE its membership changes begin. ChatGPT Ads refuses once the operation is already applying changes, and that refusal is reported as it came. Free. The reply is the status read back after the cancel, never the request alone.

cancel_scheduled

ChatGPT
Remove a queued post before it goes out. Get the id from list_scheduled. Only works while it is still queued — something already published cannot be unsent (use edit_facebook_post / delete_meta_post / set_facebook_video_cover to delete a Facebook/Instagram post after the fact).

cancel_stripe_subscription

ChatGPT
Cancel a Stripe subscription: at the end of the current period by default (the customer keeps access until then), or immediately:true to end it now. ALWAYS needs confirm:true, test keys included. The read-back distinguishes the two — a period-end cancel reports the date it will end and the status it keeps until then, an immediate one reports status canceled. Needs Stripe connected (Settings > Connectors > Stripe).

cancel_x_broadcast

ChatGPT
Use this when a scheduled X broadcast should be deleted. Cannot be undone; needs confirm:true. rollForward:true shifts a recurring series instead of leaving a gap. Needs X connected; X bills per call, so it costs credits.

change_voice

ChatGPT
Swap the narration of a finished video into a different voice — keeps the performance, lip-sync, and background sound. Use when the user likes the video but wants a different narrator voice; use dub_video only for language translation. Paid; returns the served URL.

check_ad_policy

ChatGPT
Pre-flight ad copy against Meta's REAL, live Advertising Standards before you run it — a flat 1-credit check. Pulls Meta's actual policy pages and returns a verdict (pass / fix / block) where every flagged issue QUOTES Meta's own policy text verbatim plus a compliant rewrite that keeps the sell. It's a check, not an edit — it never changes the creative. Especially worth running for regulated-adjacent categories (health/supplements, weight-loss or beauty results claims, finance/crypto/insurance, alcohol, dating, gambling) or ANY strong/absolute/guaranteed claim.

check_analytics_compatibility

ChatGPT
FREE PRE-FLIGHT FOR analytics_report — ask Google whether these dimensions and metrics can appear in ONE report together, BEFORE running the report that would fail. Use it whenever a report combines fields you have not combined before, and especially when one of them is a custom dimension: GA4 has real pairs that are each perfectly valid alone and cannot be queried together, and analytics_report deliberately forwards names AS GIVEN (GA4 publishes hundreds of dimensions and every property ships its own custom ones, so validating against a copied list would go stale and start REFUSING names Google accepts). Pass EXACTLY what you would pass to analytics_report — same metrics, same dimensions, same filters — and it returns which fields are INCOMPATIBLE so you can drop one and run the real report once. IT DOES NOT CHECK THAT A NAME EXISTS: an unknown api name is refused by Google outright, and list_analytics_metadata is what resolves names. Read-only, 0 credits.

check_apple_ads_app_eligibility

ChatGPT
Ask Apple whether an app may be advertised at all, PER PLACEMENT AND PER COUNTRY — the check Apple tells you to run before building a campaign around an app. Each row is ELIGIBLE or INELIGIBLE for one combination of placement, country or region and device class, with the minimum age rating for that market. A campaign targeting a country the app is INELIGIBLE in simply will not deliver there and nothing in the campaign build says so, so this is how you find out for free beforehand. adamId is REQUIRED. NO ROWS IS NOT THE SAME AS INELIGIBLE — it usually means the app is not owned by this organization; report that distinction rather than presenting it as a refusal. Read-only, free.

check_tiktok_creator_status

ChatGPT
Whether particular TikTok handles have joined TikTok One, up to 20 at a time, with each answered IN (can be invited), NOT_IN (a real account that has not joined) or INVALID (not a handle TikTok knows). WORTH CALLING BEFORE INVITING ANYONE: a creator who has not joined cannot be invited, and TikTok reports that as a 200 with the handle in a failed list rather than as an error; so an unchecked invite looks like it worked and quietly reached nobody. Read-only, free.

clear_sheet_range

ChatGPT
Empty a range of cells in a Google Sheet, leaving the rows themselves in place. DESTRUCTIVE: call it WITHOUT confirm first and nothing is cleared — you get back the real number of filled cells in that exact range. Show the user that number, get an unambiguous yes, then call again with confirm:true AND confirmCells set to it. The echo is not ceremony: it is what catches naming A1:Z1000 when you meant A1:Z10, which is the mistake that actually happens. There is deliberately NO default range. The clear is read back and reported as confirmed only if the range really is empty afterwards. To remove a whole tab instead, use manage_sheet_tabs.

clear_x_chat_disappearing_messages

ChatGPT
Use this when messages in a conversation should stop disappearing, for everyone in it. Needs X connected; X bills per call, so it costs credits.

clip_video

ChatGPT
Cut ONE long video into several RANKED, ready-to-post short clips (podcast, webinar, interview, talk, long ad cut -> Reels/Shorts/TikTok). Transcribes with timestamps, picks the strongest SELF-CONTAINED moments, then cuts + reframes each with ffmpeg — no video model renders anything, so it is fast and cheap. THE VERTICAL REFRAME IS SUBJECT-AWARE: one cheap vision call per clip (billed as its own event) picks a SINGLE crop offset held for the whole clip, so a speaker sitting camera-left is not cropped out and the framing never drifts inside a clip; with nothing to discard or no single subject it stays dead centre — read reframedToSubject and each clip's reframeWhy back rather than assuming either way. ACCEPTS: a YouTube link (or Vimeo / Loom / Dailymotion / Streamable / Rumble / Wistia / Twitch / TED), a direct https .mp4/.mov/.webm, or a Hermoso /generated/ URL (upload_file turns a local file into one). NOT supported: TikTok / Instagram / Facebook links, and anything age-restricted, private, members-only, geo-blocked or still LIVE — those fail fast with the real reason and are fully refunded, so ask for a direct file or an upload rather than retrying. Source ~15s to ~600MB; only the first ~40 minutes is analysed (truncated:true says so). Cost: a ~7-credit hold settled to the exact transcription + encode cost, plus the clip-selection model's tokens as their own small event. RETURNS clips[] — each its OWN served mp4 URL, title, hook, ready-to-post caption, 0-100 score and source timecode. SUBTITLES ARE BURNED IN BY DEFAULT (slim white CAPS, thin black outline, bottom safe band, no box) because short-form is watched on mute; captions:false for clean footage. TIMING IS APPROXIMATE, NOT WORD-LEVEL — cues follow the transcript's per-sentence timestamps, split by character count; never promise frame-accurate sync. captionsBurned counts the clips that really carry a burned track and captionNote says why any are bare.

clone_applovin_ads_creative_set

ChatGPT
Copy an AppLovin creative set into another campaign, born PAUSED and read back. AppLovin only clones a set that already belongs to a campaign. Needs the Campaign Management key, which exists once AppLovin enables API access on the account; without it the answer says how to get it.

clone_static

ChatGPT
One-click STATIC-AD CLONE (the web app calls it Clone): rebuild a competitor/reference STATIC (image) ad as an on-brand version — SAME layout, composition and energy, but YOUR product, brand colours, logo and voice, with every trace of the source brand removed. Pass imageUrl = the static ad image to clone. Uses your saved brand (pass brandId to target a specific profile — that switches this key's active profile like use_brand). IMAGES ONLY — for a video ad use remake_video_for_brand with its link, then render_ad. Bills as one image generation.

collect_post_metrics

ChatGPT
Fetch fresh performance numbers for this brand's recorded posts and store them as a time-series. Metrics ACCRUE, so a post is read at ~24 hours and again at ~7 days; this collects whichever readings are due and skips the ones already taken. A channel that cannot report a metric records it as ABSENT with the reason — never as zero — and a read that fails is recorded as 'could not tell', which contributes to nothing. X IS SKIPPED BY DEFAULT because X bills us per API call: pass includeMetered:true to include it, and tell the user it costs credits BEFORE you do. The skip is always reported so a channel missing from the numbers is never mistaken for one that performed badly. Free except for X.

comment_on_meta_post

ChatGPT
Leave a NEW top-level comment on one of the brand’s own Facebook Page posts, as the brand. This is the "link in the first comment" tactic — Facebook suppresses reach on a post with an outbound link in the body, so brands put the link underneath — and it is also how you add a pinned clarification, an offer code or a correction without editing the post. It is PUBLIC and posted as the brand: show the user the exact wording and get their go-ahead first. postId comes from list_meta_posts. FACEBOOK ONLY: Meta does not let an app add a top-level comment to an INSTAGRAM post — the only comment write Instagram documents is a reply to a comment somebody else left (reply_to_meta_comment) — so an Instagram media id is refused by name rather than attempted. Free.

comment_on_tiktok_video

ChatGPT
Write a NEW top-level comment on a TikTok post the authorized account owns. Text (≤1,200 characters, UTF-8) or an image, and TikTok requires at least one of the two. AN IMAGE HERE MUST BE UPLOADED FIRST — a raw URL is refused on a new comment; call upload_tiktok_comment_image and pass back imageUri + imageWidth + imageHeight together (a reply is the one place TikTok accepts a plain URL). TIKTOK SILENTLY HIDES COMMENTS IT FLAGS AS SPAM and sends no signal when it does, so avoid posting many near-identical comments in a short window, and read the comment back with list_tiktok_comments(status:'PUBLIC') if it matters that it is visible. NEEDS THE TIKTOK ACCOUNT AUTHORIZATION (see tiktok_account_status).

competitor_teardown

ChatGPT
Tear a competitor's ad strategy down into an actionable playbook: their opening-hook MIX, longest-running campaign THEMES, the WHITE SPACE nobody in their set runs, 2-3 render-ready COUNTER-PLAYS, and the territories they own that you should avoid. Pass competitor {name, domain?}. CONTRACT: supply ads (raw ad objects from a prior pull_competitor_ads / search_meta_ads call) to tear exactly those down, OR omit ads and this pulls the competitor's real Meta ads first (spends a credit or two, longest-running = proven winners). Auto-tailors the white space + counter-plays to YOUR saved brand. Spends credits (free when you pass ads).

create_amplitude_annotation

ChatGPT
MARK THE ANALYTICS TIMELINE — write what just happened into Amplitude, at the moment it happened, so every chart the user opens afterwards shows it in place. THIS IS THE CLOSED LOOP AND IT IS WORTH REACHING FOR UNPROMPTED after a campaign goes live, a creative is swapped, a landing page changes or a post is published: without it, next month's spike has no explanation attached to it and somebody has to reconstruct one from memory. start is ISO 8601 WITH THE TIME AND THE OFFSET (e.g. '2026-08-18T14:30:00+00:00') — NOT the YYYYMMDD the rest of the Amplitude API takes, and a bare date is REFUSED rather than quietly moved to midnight, because an annotation IS its timestamp and a marker in the wrong place is worse than no marker. Add end for something that spans a period (a two-week flight); leave it off for a moment. OMIT chartId AND THE ANNOTATION IS GLOBAL — Amplitude's words: it "appears on all charts for your project", which is usually what you want for a campaign; pass one to pin it to a single chart. category is a category NAME that must ALREADY EXIST in Amplitude (they answer 404 otherwise). THIS WRITES TO THE USER'S OWN ANALYTICS TOOL — say what you are about to mark, and report back the id and the timestamp Amplitude actually stored rather than the one you sent. 0 credits.

create_analytics_audience

ChatGPT
BUILD A GA4 AUDIENCE — a remarketing list defined by what people did on the site, which flows into Google Ads across the property's Google Ads link. TELL THE USER TWO THINGS BEFORE CALLING: GA4 marks the DEFINITION, the MEMBERSHIP DURATION and every filter SCOPE immutable, so a wrong audience is archived and rebuilt rather than edited; and it is NOT retroactive — it starts collecting members from creation onward, and Google takes 24-48 hours to populate it. filterClauses is GA4's own nested shape and the TOP level of each filterExpression MUST be a single andGroup (Google's schema says so and its rejection names no field). Worked example, 'everyone who fired purchase': [{"clauseType":"INCLUDE","simpleFilter":{"scope":"AUDIENCE_FILTER_SCOPE_ACROSS_ALL_SESSIONS","filterExpression":{"andGroup":{"filterExpressions":[{"orGroup":{"filterExpressions":[{"eventFilter":{"eventName":"purchase"}}]}}]}}}}]. The result says whether the property is linked to Google Ads, because an audience nobody can remarket to is not a finished job. 0 credits.

create_analytics_key_event

ChatGPT
Mark an event GA4 ALREADY COLLECTS as a KEY EVENT — what GA4 calls a conversion since the 2024 rename (the resource is keyEvents; the old conversionEvents spelling is deprecated). This is what makes an event countable as a result in reports and importable into Google Ads as a conversion. THE EVENT NAME MUST MATCH AN EVENT THE SITE ALREADY SENDS ("purchase", "generate_lead", "sign_up", or a custom one) — marking an event that is never fired creates a key event that can never fire, so check list_analytics_definitions or an analytics_report broken down by eventName first. countingMethod ONCE_PER_EVENT counts EVERY occurrence; ONCE_PER_SESSION counts the event at most ONCE PER SESSION, however many times it fires in that session. Those are the only two GA4 accepts — there is no per-USER counting method, and asking for one is refused rather than quietly counted per event. The result is READ BACK from Google, never echoed from the request. The property is the NUMERIC id from list_analytics_properties. Needs the Google Analytics connection to have edit access on the property.

create_apple_ads_ad

ChatGPT
Create an Apple Ads ad — the object that shows one creative in one ad group. REQUIRED: adGroupId, creativeId (from create_apple_ads_creative) and name. CREATED PAUSED ALWAYS, with no override, so it cannot spend until set_apple_ads_status(level:"ad", status:"ENABLED", confirm:true) and its parents are enabled too. THE PLACEMENT RULE, which Apple documents nowhere and which is checked FOR FREE before anything is written: an ad attaches only to a campaign whose placement is APPSTORE_SEARCH_TAB. Apple refuses ads on APPSTORE_SEARCH_RESULTS, APPSTORE_TODAY_TAB and APPSTORE_PRODUCT_PAGES campaigns with AD_CAMPAIGN_SUPPLY_SOURCES_NOT_SUPPORTED — and a SEARCH RESULTS campaign needs no ad and no creative at all, because Apple renders your App Store product page itself, so such a campaign is already complete. The creative must be VALID; a PENDING one is a wait, not a failure. Only ONE ad per ad group can be ENABLED at a time, so enabling this one means pausing whichever ad serves there now. Its creative and ad group are locked at creation: to serve a different creative, create a NEW ad and delete this one, which is Apple’s own instruction so delivery history stays traceable. NO CONFIRMATION IS NEEDED even inside a live ad group, and that is deliberate rather than an oversight: a paused ad cannot serve and does not displace whichever ad is serving there, so gating it would be friction with no safety behind it. Free.

create_apple_ads_ad_account

ChatGPT
Create a new ad account under this Apple Ads organization — the container an agency opens per client, or a business opens to run Apple Maps ads alongside App Store ones. AN AD ACCOUNT SPENDS NOTHING: it holds campaigns, and every campaign inside it is still born PAUSED. productFeatures is REQUIRED, is a CHOICE OF ONE, and is IMMUTABLE FOREVER — APPSTORE_APP_MANUAL for App Store advertising or BUSINESS_BRAND_MANUAL for Apple Maps, never both, and an organization needing both keeps two accounts. Delegations must MATCH that choice: an App Store account delegates a CONTENT_PROVIDER (a CPID from list_apple_ads_advertiser_resources), a Maps account delegates a BUSINESS_BRAND — a mismatch is refused for free here, because Apple accepts it and you get an account that cannot advertise anything. currency, timeZone and paymentModel are inherited from the organization and cannot be set. NOTE: Hermoso keeps writing to the ONE ad account this connection is pinned to — reconnect Apple Ads and choose the new account to build inside it. Free.

create_apple_ads_ad_group

ChatGPT
Create an ad group inside an Apple Ads campaign — this is where the bid and the keywords live. CREATED PAUSED ALWAYS. REQUIRED: campaignId, name. defaultBid is the most you will pay per tap (decimal string, e.g. "1.50") and applies to every keyword with no bid of its own. startTime is optional here and defaults to now: Apple documents it as optional and then REJECTS the create without it, so Hermoso always supplies one. searchMatch:true opts into Apple’s automated keyword matching, which targets relevant search terms beyond your keyword list. pricingModel defaults to CPT and must match the campaign’s billing event. Keywords cannot be created inline — make the ad group, then call add_apple_ads_keywords. targeting is who and where it reaches: FOR AN APPLE MAPS AD GROUP, targeting.locationGroupIds is the only field that says WHICH of the brand’s shops this ad group promotes, and an ad group with none stops serving rather than reaching everywhere — build the group first with create_apple_ads_location_group. Radius and geo targeting cannot be combined in one ad group and that is refused here rather than delivered on terms nobody chose. Like the campaign it lives in, it stays PAUSED until set_apple_ads_status(status:"ENABLED", confirm:true) arms it. Free.

create_apple_ads_budget_order

ChatGPT
A BUDGET ORDER NEEDS A LINE OF CREDIT ORGANIZATION. Measured on prod 2026-08-17 against org 19534840 (PAYG): Apple answers 403 “The organization does not have a Line of Credit(LOC). Creating Budget Order is permissible only for LOC Organizations. [FORBIDDEN]” — so on a pay-as-you-go account this whole family is unusable and no request shape fixes it. That is Apple’s account tier, not a Hermoso limit and not something to reconnect over. Create a shared spending ceiling that campaigns can draw from. A BUDGET ORDER SPENDS NOTHING AND CANNOT MAKE ANYTHING SERVE — it is a cap, so creating one can only ever reduce what campaigns may spend, never raise it, which is why it takes no confirmation. Nothing draws from it until you set a campaign’s budgetOrderId. TWO APPLE RULES ARE ENFORCED FOR FREE BEFORE ANYTHING IS SENT, because Apple’s own 400s name the field rather than the rule: startTime must be TOMORROW OR LATER at midnight UTC (today is rejected outright), and exactly ONE ad account may be attached — which is why adAccountId is a single value and defaults to the account this connection is pinned to. Omit endTime for an open-ended budget. Apple requires invoice contact details, so primaryBuyerEmail and billingEmail are required and are never invented. Free.

create_apple_ads_campaign

ChatGPT
Create an Apple Ads (Apple Search Ads) campaign promoting an iOS app on the App Store. CREATED PAUSED ALWAYS — there is no override, so nothing can spend until you separately call set_apple_ads_status(status:"ENABLED", confirm:true). REQUIRED: name (unique across the whole Apple Ads ORGANIZATION, not just this ad account — Apple 400s DUPLICATE_CAMPAIGN_NAME, and the org can contain campaigns you cannot see), appAdamId (the App Store id, e.g. "6752439949" — read it off any existing campaign’s promotedObjectId), dailyBudget (decimal string, e.g. "25.00"), countries (ISO 3166-1 alpha-2). placements defaults to APPSTORE_SEARCH_RESULTS, the classic Search Ads placement, which needs NO creative — Apple renders your App Store product page. bidStrategyType defaults to MANUAL_CPT (a fixed price per tap); MAX_CONVERSIONS optimizes for installs and is the only strategy that takes a campaign-level bid, where it means the target CPA. A MANUAL_CPT per-tap bid does NOT belong here: Apple refuses it at campaign level with an error naming a field you never sent — pass defaultBid to create_apple_ads_ad_group instead. Free.

create_apple_ads_creative

ChatGPT
Create an Apple Ads creative — what a person sees before and after tapping the ad. Pick creativeType: DEFAULT_PRODUCT_PAGE renders the app’s standard App Store listing and needs only adamId; CUSTOM_PRODUCT_PAGE renders a custom page built in App Store Connect and needs adamId AND productPageId (from list_apple_ads_product_pages); LOCAL_ADS_SEARCH_CREATIVE is Apple Maps and needs brandId (from list_apple_ads_brands) plus assetIds you uploaded with upload_apple_ads_asset — that is the one creative type where YOUR OWN IMAGE is the ad. YOU CANNOT SUPPLY YOUR OWN IMAGERY FOR AN APP STORE AD — Apple renders the pre-tap ad from the product page itself, so the way to change how an App Store ad LOOKS is to edit that page (or build a Custom Product Page) in App Store Connect; say that plainly rather than looking for a parameter that does not exist. The destination type is derived from the creative type and never asked for. A creative SPENDS NOTHING and has no status of its own: it serves only once an ad references it and that ad, its ad group and its campaign are all ENABLED. Apple validates asynchronously, so a fresh creative is usually PENDING for a moment and an ad may only reference a VALID one — that is a wait, not a failure. Type and destination are immutable afterwards. Free.

create_apple_ads_location_group

ChatGPT
Create a location group — the set of a brand’s shops an Apple Maps ad group promotes. TWO KINDS. STATIC takes an explicit locationIds list (from list_apple_ads_locations) and is VALID immediately. DYNAMIC takes rules and maintains itself as the brand opens and closes shops, but starts PENDING while Apple evaluates them — wait for systemStatus VALID before targeting it. A RULE THAT MATCHES NOTHING IS APPLE’S ONE SILENT FAILURE HERE: it returns no error and simply builds a group with groupTotal 0, so adminArea must be the FULL English name ("California", never "CA") and locality must be countryOrRegion|adminArea|locality ("US|New York|Brooklyn", never "Brooklyn") — both shapes are refused here before the group is built. A group SPENDS NOTHING and does not decide who sees the ad or how near they are: it only scopes WHICH shops the ad group promotes, while radius and geo targeting on the ad group decide reach. brandId and the ad account are locked in permanently at creation. Target it afterwards with create_apple_ads_ad_group(targeting.locationGroupIds) or update_apple_ads_object(level:"adgroup"). Free.

create_applovin_ads_campaign

ChatGPT
Create an AppLovin Ads campaign, read back PAUSED: AppLovin ignores status on create, so Hermoso pauses it straight after. A WEB campaign needs name, websiteUrl, countries, dailyBudget, goalType and goalValue (CPE also eventTarget; CHK_ROAS also roasDayTarget). APP campaigns add platform, packageName, trackingMethod, clickUrl, impressionUrl (iOS: itunesId). Every field is checked before AppLovin is called. Needs the Campaign Management key, which exists once AppLovin enables API access on the account; without it the answer says how to get it.

create_applovin_ads_creative_set

ChatGPT
Create an AppLovin creative set from uploaded asset ids, born PAUSED, optionally inside a campaign. It must hold EITHER an HTML playable OR a portrait image (IMG_INTER_P) plus a video; Hermoso checks that against AppLovin’s own asset types before creating it. Needs the Campaign Management key, which exists once AppLovin enables API access on the account; without it the answer says how to get it.

create_brand

ChatGPT
Add a NEW profile (a brand, client, creator or personal workspace) and switch to it. Each has its OWN brand details, memory, swipefile, Library, avatars, skills, playbooks and connectors; nothing leaks between them. Use draft_brand to FILL it and update_brand to edit it. Re-running with the same name returns the existing profile instead of a duplicate. Creating it is free. Drafting a NEW brand into it (draft_brand) needs the credits for its research on the balance, about 100; with fewer, draft_brand refuses before anything runs or is charged and says how many it needs and how many the account has.

create_doc

ChatGPT
Create a new Google Doc in the user’s Drive with a title + optional body text — e.g. export ad copy, a creative brief, or a report. Returns the document id + URL. Needs Google Drive connected (Settings > Connectors > Google Drive — one connection covers Drive, Sheets and Docs).

create_drive_folder

ChatGPT
Create a folder in the user’s Google Drive (optionally nested under parentId) to organize saved files. Returns the folder id + webViewLink. Use that ID as update_drive_file’s moveToFolderId or as parentId for a nested folder. NOTE: save_to_drive’s folder is a NAME, not this id — it find-or-creates a folder by that name, so pass the folder NAME there (or omit and just save, then move with update_drive_file).

create_google_ads_ad

ChatGPT
Create the actual AD inside a Google Ads ad group — this is the object that carries the creative; a campaign or ad group alone shows nothing. On a SEARCH campaign it builds a RESPONSIVE SEARCH AD: 3–15 headlines (≤30 chars), 2–4 descriptions (≤90 chars), at least one finalUrl, optional path1/path2. On a DISPLAY campaign it builds a RESPONSIVE DISPLAY AD: headlines, longHeadline, descriptions, businessName plus BOTH a landscape (1.91:1) and a square (1:1) image asset from upload_google_ads_asset. The right format is chosen from the campaign's channel. Created PAUSED and read back from Google. If the parent campaign is already LIVE (ENABLED), creating this ENABLED starts REAL AD SPEND immediately — show the user what would begin serving, get an explicit yes, then pass confirm:true. Leaving it PAUSED never needs confirmation.

create_google_ads_ad_group

ChatGPT
Add an ad group to an EXISTING Google Ads campaign — the level between a campaign and its ads. Google requires it: a campaign with no ad group cannot serve. Optionally build its ad and keywords in the same ATOMIC call. The ad-group type is taken from the campaign's channel automatically. Created PAUSED and read back from Google before you are told it exists. If the parent campaign is already LIVE (ENABLED), creating this ENABLED starts REAL AD SPEND immediately — show the user what would begin serving, get an explicit yes, then pass confirm:true. Leaving it PAUSED never needs confirmation.

create_google_ads_conversion_action

ChatGPT
Create a CONVERSION ACTION — the thing that tells Google what counts as a result on this account. This is a PREREQUISITE, not a nicety: MAXIMIZE_CONVERSIONS, MAXIMIZE_CONVERSION_VALUE, TARGET_CPA, TARGET_ROAS and every Performance Max campaign are undeliverable without one, because Google has nothing to optimise toward. type WEBPAGE (a purchase / lead / signup on the site — the normal choice), UPLOAD_CLICKS or UPLOAD_CALLS; every other Google conversion type (Firebase, Google Analytics 4, Floodlight, store visits) is READ-ONLY and is created in those products, not here. Set category to what actually happened (PURCHASE, SUBMIT_LEAD_FORM, SIGNUP, BOOK_APPOINTMENT…) and defaultValueUsd when a conversion has a known worth — TARGET_ROAS has nothing to maximise without a value. Created ENABLED and counted in "conversions" by default, because a conversion action that is neither records nothing. It CANNOT SERVE AN AD and cannot spend a cent, so it needs no confirmation. A WEBPAGE action records NOTHING until its Google tag is installed on the site — say that when you report it.

create_linkedin_ads_campaign_group

ChatGPT
Create a LinkedIn CAMPAIGN GROUP — the container LinkedIn has required every campaign to live inside since 2020. Created DRAFT, which is LinkedIn’s own structural safety net: it REFUSES to hold an ACTIVE campaign inside a DRAFT group, so while the group is a draft nothing beneath it can serve whatever its own status says. Creating it ACTIVE removes that protection and therefore requires confirm:true. LinkedIn REQUIRES a run schedule on a campaign group: it starts today unless you pass startDate, and if you set totalBudget you MUST also pass endDate — that pairing is LinkedIn’s own rule and it is refused here before anything is created. Read back from LinkedIn before you are told it exists.

create_linkedin_ads_creative

ChatGPT
Create the AD ITSELF on LinkedIn — a CREATIVE inside an existing campaign. A LinkedIn campaign holds no copy and no visual, so until this runs the campaign CANNOT show an impression no matter what its status says; say that rather than calling a campaign "live". Two ways in: pass postUrn to sponsor a post that already exists (LinkedIn’s "boost this post"), or pass text and/or imageUrl / videoUrl to author a DIRECT SPONSORED CONTENT post — a real post by a company Page the user administers that is NEVER shown on the Page’s feed and exists only as an ad. Created DRAFT: it spends nothing until you activate it with set_linkedin_ads_status(level:"creative", status:"ACTIVE", confirm:true). Creating it ACTIVE under an already-live campaign starts REAL AD SPEND on the very next auction and therefore requires confirm:true. The whole tree — group, campaign and every creative — is read back from LinkedIn before you are told anything exists.

create_linkedin_event

ChatGPT
Create a LinkedIn LIVE event on a company Page the brand administers. Warning: THIS REQUIRES LINKEDIN LIVE ACCESS on the member’s account: liveVideo is the only online format LinkedIn documents for event creation, and a member without Live access is refused with “The member does not have LINKEDIN_LIVE creation access” (measured live 2026-08-31). Check the account has it before offering this. Warning: CREATED BUT NOT ANNOUNCED: 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. ONLINE events only for now — an in-person event needs a full address object and a custom background image needs a three-step upload, and neither is offered rather than failing at LinkedIn. Needs the Events permission (administrator flag) and a reconnect, the same two conditions as list_linkedin_events. 0 credits.

create_merchant_data_source

ChatGPT
Create an API product feed on a Merchant Center account — the container upsert_merchant_product writes into. Most accounts have none until one is made: a store set up through the Merchant Center UI has a UI feed, which is read-only. feedLabel and contentLanguage must be set TOGETHER or not at all and BOTH ARE IMMUTABLE — leave them off and the feed accepts products for any market and language, which is the safer default because a wrong feed label can never be corrected. Omit destinations to inherit wherever the account already sells. Creating a feed costs nothing and cannot spend.

create_merchant_promotion

ChatGPT
PUT A SALE BADGE ON THE MERCHANT'S SHOPPING LISTINGS — the most seasonal thing a retail advertiser does. It creates the promotion and resolves (or creates) the promotion DATA SOURCE that country/language needs automatically; a promotion data source is its own kind and cannot be the product feed. IT IS NOT LIVE ON CREATE: Google validates promotions asynchronously and can take days to approve one, so report it as SUBMITTED and re-read with list_merchant_promotions. Everything Google marks Required is refused up front by name, including the pairings its own 400 does not mention — PERCENT_OFF needs percentOff, MONEY_OFF needs moneyOffAmount, GENERIC_CODE needs genericRedemptionCode — because otherwise Google accepts the promotion and disapproves it hours later where nobody is watching. 0 credits.

create_messenger_marketing_campaign

ChatGPT
The container a paid Messenger marketing message is sent from (Meta act_<AD>/message_campaign). Budgets in USD; Meta bills per DELIVERED message against the campaign budget, and creating it sends and charges nothing. A NEW campaign cannot send for about an hour (61 minutes measured 2026-09-14) while Meta populates and prepares it (2300012 then 2300041, each with remaining_seconds), so create it ahead of time or reuse one from list_messenger_marketing_campaigns. dailyBudgetUsd OR lifetimeBudgetUsd, and Meta refuses either under USD 30 (code 1885272, measured 2026-09-14) (omit both and Meta sets an estimated daily cap; Meta may spend up to 175% of a daily budget on one day, never more than 7× per week). REGION LAW: Meta lets us serve businesses in 20 countries only — US, Mexico, Brazil, India, Australia, Singapore, UAE, Saudi Arabia, Hong Kong, Taiwan, Thailand, Malaysia, Indonesia, Philippines, Vietnam, New Zealand, Chile, Colombia, Peru, Israel — NOT Canada, the EU or the UK — and cannot deliver to people in the EU, UK, Japan, South Korea or Australia. Political Pages are excluded. dryRun:true shows the exact body.

create_meta_adset

ChatGPT
Create an AD SET on an EXISTING Meta campaign — the level that holds the audience, budget, schedule and bidding. Use it to hang SEVERAL ad sets off ONE campaign, which is how you actually test audiences on Meta (one ad set per audience, same campaign, same creative). Takes the same full targeting, pixelId/conversionEvent, bidStrategy, schedule and budget fields as create_meta_ad. Created PAUSED and read back from Meta. It has NO ads until you call create_meta_ad(adSetId:…). For CATALOG / ADVANTAGE+ CATALOG ads (DPA), pass productSetId here — the campaign carries productCatalogId and the ad set narrows it to a product set.

create_meta_audience

ChatGPT
Build a retargeting audience on a connected Meta ad account. Three kinds: kind:"website" (people whose visited URL contains urlContains, seen by pixelId — pass the brand’s own domain for "all visitors"; retentionDays up to 180), kind:"engagement" (people who did event on the brand’s Facebook Page, or its Instagram business profile with source:"instagram"; retentionDays up to 730), or kind:"lookalike" (sourceAudienceId + country + ratio 0.01–0.20, lookalikeType "similarity" or "reach"). CREATING AN AUDIENCE SPENDS NOTHING — it is a definition; money only moves when an ad set targets it and that campaign is activated through set_meta_campaign_status(confirm:true). Meta needs roughly 30 minutes and ~1,000 people before a new audience can be targeted, so a fresh one reporting no size is normal. Customer-list uploads are deliberately NOT supported here (hashed personal data + Meta’s Custom Audience Terms) — send the user to Ads Manager for those.

create_meta_campaign

ChatGPT
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.

create_meta_catalog

ChatGPT
Create a NEW product catalog on one of the merchant’s Meta business portfolios. A catalog is the input to Advantage+ catalog ads (dynamic product ads) — build it first, then fill it from the merchant’s feed or Shopify, then point a campaign at it. Name the owning portfolio EITHER by its numeric businessId OR by businessName, exactly as list_meta_catalogs prints it (“DMDB Group”) — a name that matches no portfolio, or more than one, is refused BY NAME with the portfolios that really exist and their ids, never guessed, and it never falls back to “the only portfolio”. Vertical defaults to commerce and CANNOT be changed afterwards, nor can the owning portfolio, so both are worth getting right the first time. IMPORTANT — META DOES NOT REFUSE A DUPLICATE NAME: it silently creates a SECOND catalog with the same name and nothing to tell the two apart, so Hermoso refuses a name the portfolio already uses and tells you the existing id (if a previous create seemed to fail, that id is very likely the catalog it actually made). Pass allowDuplicateName:true if you genuinely want a twin. The confirmation is READ BACK from Meta — its create returns only an id, so the name, vertical and portfolio in the summary were fetched, not echoed. Free, 0 credits.

create_meta_lead_form

ChatGPT
Create an INSTANT LEAD FORM on a connected Facebook Page — the in-app form a Meta lead ad opens instead of sending someone to a website, so a person can sign up without leaving the app. questions takes Meta’s own type names, e.g. ["FULL_NAME","EMAIL","PHONE"]; for anything bespoke pass {type:"CUSTOM", label:"What size fleet do you run?"} and add options:[…] to make it a dropdown. Ask FEWER questions than you think — every extra field costs completions. privacyPolicyUrl is REQUIRED: the form collects real people’s contact details and has to say where their data goes. Optional: headline, contextCard {title, bullets[], buttonText} for the why-should-I screen, thankYou {title, body, buttonType, buttonText, websiteUrl}, locale, followUpActionUrl. CREATING A FORM SPENDS NOTHING and publishes nothing — it is invisible to the public until an ad points at it (create_meta_ad with objective:"OUTCOME_LEADS" + leadFormId), and that ad is created PAUSED. Read the submissions with read_meta_leads. NEEDS pages_manage_ads — see list_meta_lead_forms for what to do if Meta refuses it.

create_meta_pixel

ChatGPT
Create a META PIXEL on one of the brand’s ad accounts, so their ads can optimise for real website conversions instead of link clicks and so website retargeting audiences become possible. A pixel is a DEFINITION: it cannot serve, cannot spend, and RECORDS NOTHING until its snippet is installed on the site — the reply hands back that snippet and says so. TWO THINGS TO SAY OUT LOUD BEFORE CALLING IT: Meta allows exactly ONE pixel per ad account (a second attempt is refused and Hermoso will name the one that already exists — so call list_meta_pixels first), and Meta publishes NO WAY TO DELETE a pixel, so this is permanent. Creating it spends nothing.

create_microsoft_ads_ad

ChatGPT
Create the actual AD inside a Microsoft Advertising ad group — this is the object that carries the creative; a campaign or ad group alone shows nothing. It builds a RESPONSIVE SEARCH AD: 3–15 headlines (≤30 chars), 2–4 descriptions (≤90 chars), at least one finalUrl, optional path1/path2. Expanded text ads CANNOT be created any more — Microsoft rejects them outright. Created Paused; if the parent ad group and campaign are both Active, creating this Active starts REAL AD SPEND immediately — get an explicit yes and pass confirm:true. Read back from Microsoft, including its editorial status, before you are told it exists.

create_microsoft_ads_ad_group

ChatGPT
Add an ad group to an existing Microsoft Advertising campaign — optionally with its responsive search ad and keywords in the same call. Created Paused by default. If the parent campaign is already LIVE (Active), creating this ad group Active starts REAL AD SPEND on the next auction, exactly like activating it — show the user what would begin serving, get an explicit yes, then pass confirm:true. Leaving it Paused never needs confirmation. Read back from Microsoft before you are told it exists.

create_microsoft_ads_customer_list

ChatGPT
Create an EMPTY Customer Match list on a Microsoft Advertising account, ready for apply_microsoft_ads_customer_list to fill. membershipDuration is 1–390 days, or -1 for no expiration (Microsoft's default is 30). scope 'Account' (the default) makes it usable only by that ad account; scope 'Customer' makes it usable by every account under the manager — Hermoso derives the right parent id for whichever you choose, because getting that pair wrong mis-parents the list silently. An audience is a definition: it cannot spend and it cannot serve on its own. Read back from Microsoft. 0 credits.

create_onedrive_folder

ChatGPT
Create a folder in the user’s OneDrive (optionally nested under parentId) to organize saved files. Returns the folder id + webViewLink. Use that id as update_onedrive_file’s moveToFolderId or as parentId for a nested folder. NOTE: save_to_onedrive’s folder is a NAME (find-or-created), not this id.

create_openai_ads_ad

ChatGPT
Create the actual AD inside a ChatGPT Ads ad group — the object that carries the creative; a campaign or ad group alone shows nothing at all. The creative is a TEXT + IMAGE CARD: a title of 3–50 characters, body copy of 100 characters maximum, one landing page URL and one still image. THERE IS NO VIDEO FORMAT ON THIS PLATFORM — a video URL is refused outright, so never offer one. Created paused; creating it active inside a live ad group starts REAL AD SPEND, so that needs confirm:true. OpenAI REVIEWS every ad (usually a few minutes) and the ad is read back with its review status: until that says approved the ad CANNOT show, so report the review status rather than calling it live. The landing page is also checked against robots.txt for OAI-AdsBot / OAI-SearchBot blocks — a page that blocks those agents cannot run ChatGPT ads at all, and the note says so.

create_openai_ads_ad_group

ChatGPT
Add an ad group to an existing ChatGPT Ads campaign. Created PAUSED by default. Bidding: pass maxBid for a fixed bid, or bidStrategy "maximize_clicks" / "maximize_conversions" to let ChatGPT Ads set the bid for the budget (OpenAI’s "Maximize results"), in which case maxBid is optional. Its context hints ARE the targeting on this platform: up to 2,000 natural-language descriptions of the conversations, topics or questions where this offering is relevant — not exact-match keywords, and no guarantee of delivery. Write many distinct ones rather than variations of the same phrase. If the parent campaign is already LIVE (active), creating this ad group active starts REAL AD SPEND on the next auction, exactly like activating it — show the user what would begin serving, get an explicit yes, then pass confirm:true. Leaving it paused never needs confirmation. The whole tree is READ BACK from OpenAI before you are told it exists.

create_openai_ads_audience

ChatGPT
Create a ChatGPT Ads custom audience from a customer list. Pass plain emails and/or phone numbers: Hermoso NORMALISES AND SHA-256 HASHES THEM LOCALLY and uploads only the digests, so no plaintext personal data leaves Hermoso. OR pass fileUrl: a public link to a UTF-8 .csv or .txt customer list (a CSV needs a header naming its column: email, phone_number, email_sha256, phone_number_sha256 or gaid; identifierResolution "auto" reads several columns at once). A file is uploaded to ChatGPT Ads as it is and OpenAI hashes raw emails and phones itself; the members list is hashed here first. Matching is asynchronous, so read the audience back for its matched count. Values that are neither an email nor a phone number are skipped and counted, never silently dropped. An audience is a definition and cannot spend.

create_openai_ads_campaign

ChatGPT
Build a campaign on the connected ChatGPT Ads account — the ads that appear below ChatGPT answers. ALWAYS created PAUSED at every level, with no override: it spends NOTHING until you activate it with set_openai_ads_status(confirm:true). The object graph is campaign -> ad group -> ad, and a campaign ON ITS OWN CANNOT SERVE AN IMPRESSION, so pass adGroup{name, maxBid, contextHints, ad{creative}} and this builds the whole tree. THE CREATIVE IS A TEXT + IMAGE CARD AND NOTHING ELSE — title 3–50 characters, body 100 maximum, one landing page, one still image. THERE IS NO VIDEO ON THIS CHANNEL: never offer a video ad here, and if the brand only has video, pull a frame from it first. TARGETING IS SEMANTIC: context hints are natural-language descriptions of the conversations where this ad belongs (up to 2,000 per ad group). Geo (countries / locationIds) and PLATFORMS (which of the iOS app, Android app and web the ad runs on) are the only other dimensions — leave platforms out to run on all three. They guide matching, they are NOT exact-match keywords, and they do not guarantee delivery. OpenAI’s own guidance is BREADTH — many genuinely distinct hints and many distinct title/body angles beat one message repeated — which is exactly what plan_variations and mine_angles produce. OpenAI has no atomic multi-object write available here, so the whole tree is VALIDATED before the first write; if a level below the campaign is still rejected, the campaign is left PAUSED (spending nothing) and the note says exactly what exists — nothing is archived behind your back, because archiving is irreversible. Everything is READ BACK from OpenAI before you are told it exists: print the returned note verbatim, and if it says the campaign cannot serve yet, say that rather than calling it a finished ad.

create_openai_ads_conversion_api_key

ChatGPT
Create a ChatGPT Ads Conversions API key — the credential a SERVER uses to send conversion events, which is the only way a conversion that happens off the page (an offline sale, a webhook, a mobile backend) can be counted at all. PERMANENT AND UNREPEATABLE, and both halves of that are why it is confirm-gated even though a key cannot spend: ChatGPT Ads publishes NO way to list, rotate or delete a key, so it exists on the ad account forever and Hermoso cannot clean it up; and OpenAI returns the secret EXACTLY ONCE. Tell the user both facts, get an explicit yes, then call with confirm:true — and tell them to store it in a server-side secret manager and never place it in browser code, client-visible environment variables, logs or source control. If OpenAI answers that key creation is not enabled for this ad account, that is an account-enablement answer from OpenAI (they say to contact your partner representative) — the connection is fine and nothing is broken on Hermoso’s side.

create_openai_ads_conversion_event

ChatGPT
Define what counts as a conversion on ChatGPT Ads, measured from one or more pixels. THIS IS THE PREREQUISITE for a conversion-optimised campaign: pass the returned id as conversionEventSettingIds to create_openai_ads_campaign. Creating one cannot spend and cannot serve — it is a definition, so it is not confirm-gated.

create_openai_ads_feed

ChatGPT
Create an EMPTY product feed on ChatGPT Ads with a name and the countries it serves (ISO codes). Fill it with update_openai_ads_feed_products, or set up SFTP bulk delivery with set_openai_ads_feed_sftp. inventoryType:"hotel" creates a HOTEL property feed instead (schema hotel_property_v1, filled with a full property snapshot over SFTP; an OpenAI limited beta for approved pilot advertisers, so an account outside the pilot is refused by OpenAI).

create_openai_ads_lead_form

ChatGPT
Draft a ChatGPT Ads LEAD FORM: a name, an optional privacy policy URL, and fields — each {fieldId, fieldType "text" | "choice", label, required, options (choice only)}. Created as a DRAFT with a draftRevisionId; publish_openai_ads_lead_form makes it live, then reference it from a Business Agent (leadFormId). Needs ChatGPT Ads connected.

create_openai_ads_pixel

ChatGPT
Create a ChatGPT Ads web pixel — the thing that observes actions on the site. IT RECORDS NOTHING until its snippet is installed on the site, so say that rather than implying tracking is live. MATCH RATE: the snippet’s oaiq("init", { user: {…} }) call accepts email_sha256, phone_number_sha256, external_id_sha256, first_name_sha256, last_name_sha256 (all SHA-256, lowercase hex) plus raw country, city, region and postal_code — tell the site owner to pass whatever they know about a logged-in visitor, because every field improves conversion matching. A pixel is a measurement definition and cannot spend. The next step is create_openai_ads_conversion_event, which says WHICH observed action counts as a conversion. AUTOMATIC ADVANCED MATCHING IS ON BY DEFAULT — that is OpenAI’s own default for pixels created through the Ads API since 2026-08-17, and it means the Pixel COLLECTS AND HASHES CUSTOMER INFORMATION (email, phone) from the page. Tell the user that in plain words, and pass automaticAdvancedMatching:false if they want it off. The reply reports what OpenAI actually stored: there is no endpoint to read a pixel back afterwards, so that one reply is the only record you will ever get.

create_pinterest_ads_ad

ChatGPT
Create the actual AD inside a Pinterest ad group — this is the object that carries the creative; a campaign or ad group alone shows nothing. A Pinterest ad PROMOTES AN EXISTING PIN, so pass pinId (post_to_pinterest returns one). Created PAUSED; if the parent ad group and campaign are both ACTIVE, creating this ACTIVE starts REAL AD SPEND immediately — get an explicit yes and pass confirm:true. Pinterest reviews ads: the read-back reports the review status and any rejection reason, and a REJECTED ad never serves until it is fixed.

create_pinterest_ads_ad_group

ChatGPT
Add an ad group to an existing Pinterest campaign — optionally with its ad in the same call. Created PAUSED by default. Pinterest REQUIRES every ad group to target at least one place, so targetingSpec must carry a GEO array or a LOCATION object. If the parent campaign is already LIVE (ACTIVE), creating this ad group ACTIVE starts REAL AD SPEND on the next auction, exactly like activating it — show the user what would begin serving, get an explicit yes, then pass confirm:true. Leaving it paused never needs confirmation. Read back from Pinterest before you are told it exists.

create_pinterest_ads_campaign

ChatGPT
Build a campaign on a connected Pinterest ad account. ALWAYS created PAUSED — worth knowing that Pinterest’s own API defaults new campaigns to ACTIVE, so this deliberately overrides that; it spends NOTHING until you activate it with set_pinterest_ads_status(confirm:true). Pinterest’s object graph is campaign -> ad group -> ad, and an ad PROMOTES AN EXISTING PIN, so a campaign ON ITS OWN CANNOT SERVE AN IMPRESSION: pass adGroup{name, targetingSpec, ad{pinId}} and this builds the whole tree. Pinterest has NO atomic multi-object write, so the levels are created in sequence and the campaign is ARCHIVED again if anything below it is rejected (Pinterest has no delete) — you never inherit a half-built campaign. Budgets are ordinary amounts in the ad account’s currency; the micro-currency conversion Pinterest requires is handled for you. Every ad group must target at least one place. Everything is READ BACK from Pinterest before you are told it exists; print the returned note verbatim, and if it says the campaign cannot serve yet, say that rather than calling it a finished ad.

create_pinterest_board

ChatGPT
Create a board on the connected Pinterest account. Needed because a Pin cannot exist without a board, and a NEW Pinterest business account has none — if list_pinterest_boards comes back empty, make one here rather than telling the user you can't pin. Boards are PUBLIC unless you pass privacy 'SECRET'; a Pin on a secret board is invisible to everyone, so only choose that if the user asked for it.

create_posthog_annotation

ChatGPT
MARK THE POSTHOG TIMELINE — write what just happened into PostHog at the moment it happened, so every chart afterwards shows it in place. The same closed loop as create_amplitude_annotation, and worth reaching for unprompted after a campaign goes live, a creative is swapped or a landing page changes. dateMarker is what POSITIONS the marker on the x-axis (ISO 8601); omit it and NOW is used, which is right for "this just went live" — and the timestamp PostHog actually STORED is reported back rather than the one sent. scope is project (the default — shows on everything) / organization / dashboard / dashboard_item, and dashboard and dashboard_item each need their id or the annotation attaches to nothing and appears nowhere. PostHog also publishes a recording scope which THEY THEMSELVES REJECT, so it is refused by name here rather than sent. creationType USR means a person wrote it, GIT means a bot or a deployment did. NEEDS THE annotation:write SCOPE on the personal API key — a key made before Hermoso supported annotations will not have it; PostHog names it in their refusal, and it is added in PostHog > Settings > Personal API keys without reconnecting. THIS WRITES TO THE USER'S OWN ANALYTICS TOOL. 0 credits.

create_reddit_ads_ad

ChatGPT
Create the ad itself — the object that binds a POST (the creative) to an AD GROUP (the targeting and budget). Create the post first with create_reddit_ads_post and pass its id as postId; without a post there is nothing to show. ALWAYS created PAUSED, and Reddit additionally has to APPROVE it, so a fresh ad reports effective status PENDING_APPROVAL — report that rather than calling it live. GOTCHA: a TEXT ("free form") post’s ad may not carry clickUrl at all — Reddit refuses it. The click destination for image and link ads lives on the POST’s media destinationUrl, not here.

create_reddit_ads_audience

ChatGPT
Create an empty custom audience on a Reddit ad account, then fill it with update_reddit_ads_audience_users. Reddit only supports ONE kind of audience through the API — an uploaded CUSTOMER LIST matched on hashed emails and mobile advertising ids; pixel-retargeting, engagement and lookalike audiences are built by Reddit itself in Ads Manager and cannot be created here. The audience arrives empty and stays unusable until it matches roughly 1,000 redditors, and Reddit takes up to 4 hours to show a size change and up to 36 hours to finish processing a list — so do not create, upload and then report success on reach in the same breath. Free.

create_reddit_ads_campaign

ChatGPT
Create the top tier of a Reddit ad — the campaign, which sets the OBJECTIVE everything under it optimises toward and (optionally) a lifetime spend cap. ALWAYS created PAUSED, with no override; it spends nothing until set_reddit_ads_status(confirm:true). Pick the objective deliberately, because the ad group’s bid type has to match it and it cannot be changed afterwards: CLICKS is Reddit’s name for traffic to a website (there is no TRAFFIC), CONVERSIONS optimises toward pixel events and needs a working pixel, LEAD_GENERATION optimises toward LEAD / SIGN_UP pixel events (Reddit retired onsite lead forms), IMPRESSIONS and VIDEO_VIEWABLE_IMPRESSIONS buy reach, APP_INSTALLS and CATALOG_SALES are for apps and product feeds. Reddit’s NEW names (since 2026-09-21) are accepted too: BRAND_AWARENESS (= IMPRESSIONS / VIDEO_VIEWABLE_IMPRESSIONS) and SALES (= CONVERSIONS, or CATALOG_SALES when useCatalog is true); Reddit rolls them out per account, so an account without them yet answers with Reddit’s own refusal — use the legacy name then. A campaign on its own can never serve: create an ad group under it, then an ad pointing at a post. The result is read back from Reddit.

create_reddit_ads_max_campaign

ChatGPT
Build a Reddit MAX campaign in one call: an automated campaign (campaign budget optimization, automatic bidding), its one automated ad group (seed targeting) and its one template ad, from which Reddit GENERATES the ads out of your creative-library assets. ALL CREATED PAUSED: nothing spends until the user approves and set_reddit_ads_status(confirm:true) runs on the campaign, the ad group and the template ad. Before calling, upload at least 3 HEADLINE and 2 IMAGE or VIDEO assets with upload_reddit_ads_assets (5 to 45 in total, all ACTIVE) and pass their ids, and take redditProfileId from list_reddit_ads_profiles. Every asset is read and counted by type before anything is created. Objectives: CLICKS, SALES, LEAD_GENERATION, APP_INSTALLS (and the legacy CONVERSIONS, Reddit’s old name for SALES). spendCap puts a lifetime ceiling on a DAILY_SPEND campaign. Bid strategies: BIDLESS (default), MAXIMIZE_VOLUME, TARGET_CPX (needs bidAmount). MAXIMIZE_VALUE and TARGET_ROAS cannot be set: Reddit exposes them only as read-only values. If a later step fails, what this call created is ARCHIVED and the reply says so. The reply says whether Reddit finished generating the ads (materialization); poll with get_reddit_ads_max_template.

create_reddit_ads_post

ChatGPT
Create the CREATIVE for a Reddit ad. This is the step people skip: a Reddit ad has no creative of its own — it points at a post — so a campaign and ad group with no post behind them can never serve. Types are TEXT (headline + body), IMAGE, VIDEO and CAROUSEL (up to 6 images), and an IMAGE, VIDEO or CAROUSEL post can carry body text too. For image/video/carousel pass media[] with a PUBLIC mediaUrl; Reddit fetches and validates it itself (minimum 140×140), and a video also needs a thumbnailUrl. The destination for a click rides on the media entry’s destinationUrl, NOT on the ad. Reddit’s call-to-action values are human-readable strings with spaces and capitals — "Learn More", "Shop Now", "Sign Up" — not SCREAMING_SNAKE; the error lists all of them. The post is published on the profile immediately, so show the user the exact headline and body first. A post with body text under media, or a rich-text body, is built by Reddit in the background: the answer is usually the post, and when Reddit is still building it the answer says pending with a jobId, so call this again with redditProfileId and that jobId (never re-send the post, which would make a second one).

create_reddit_ads_saved_audience

ChatGPT
Save a targeting definition under a name so every ad group can reuse it — define "our people" once, then pass savedAudienceId when creating ad groups instead of retyping communities and interests each time, and one later edit re-targets every ad group using it. Takes the same targeting block as create_reddit_ads_ad_group, so resolve community names and interest ids with search_reddit_ads_targeting first. Creates targeting only: no budget, no spend. Free.

create_sheet

ChatGPT
Create a new Google Spreadsheet in the user’s Drive and optionally fill it with rows — e.g. export a swipefile, ad list, or performance report. Pass rows as an array of row arrays (first row = headers). Returns the spreadsheet id + URL. Needs Google Drive connected (Settings > Connectors > Google Drive — one connection covers Drive, Sheets and Docs).

create_snapchat_ads_ad

ChatGPT
Create the Snapchat ad itself, inside an ad squad. CREATED PAUSED with no override. THE CREATIVE COMES FROM upload_snapchat_ads_creative: pass the creativeId it returns. AN AD POINTS AT A CREATIVE, NEVER AT A MEDIA ID — passing a mediaId is refused by name rather than failing at Snapchat with a field-path error. SNAPCHAT REVIEWS EVERY AD before it can show: the returned reviewStatus says whether that has happened, and an ad Snapchat has REJECTED cannot serve even once it is activated, so relay a rejection instead of reporting a successful build. The status is READ BACK from Snapchat’s own row — if the note carries a Warning: saying it was stored as anything other than PAUSED, relay that and pause it before anything above it is activated.

create_snapchat_ads_ad_squad

ChatGPT
Create an ad squad under an existing Snapchat campaign — the tier that holds the budget, the bid, the targeting, the placements and the schedule. CREATED PAUSED with no override. TWO THINGS ARE MANDATORY AND NEITHER IS GUESSABLE: countries (at least one two-letter code — Snapchat refuses an ad squad with no geo, and search_snapchat_ads_targeting(kind:"country") resolves them), and a budget. MONEY ON SNAPCHAT IS MICRO-CURRENCY: state a plain amount in dailyBudget (50 means fifty dollars) and Hermoso multiplies by 1,000,000 for you. Only use dailyBudgetMicro if you have ALREADY converted, and NEVER pass both — that is refused rather than resolved by precedence, because applying the conversion twice to an already-converted amount asks for a budget a million times too large, and under-converting merely fails loudly while over-converting does not. Snapchat’s documented minimum daily budget is 5 units. A bid is required unless bidStrategy is AUTO_BID, which lets Snapchat choose it.

create_snapchat_ads_campaign

ChatGPT
Create the top tier of a Snapchat ad — the campaign. CREATED PAUSED AND THERE IS NO OVERRIDE; it spends nothing until set_snapchat_ads_status(confirm:true). A CAMPAIGN ALONE CAN NEVER SERVE: it needs an ad squad and then an ad under it, and every tier must be ACTIVE before one impression is shown. Objectives are Snapchat’s CURRENT objective_v2 set — AWARENESS_AND_ENGAGEMENT, SALES, TRAFFIC, APP_PROMOTION, LEADS. A LEGACY objective name (BRAND_AWARENESS, WEB_CONVERSION and the rest) is REFUSED BY NAME rather than silently mapped onto a v2 value, because mapping one onto the other would optimise the campaign for something the user did not ask for. The result is READ BACK from Snapchat and the note says so when the read-back could not be run.

create_stripe_coupon

ChatGPT
Create a Stripe coupon: exactly one of percentOff (1–100) or amountOff (major units) + currency; duration once (default), repeating (with durationInMonths) or forever; optional name, id and maxRedemptions. Read back. LIVE key: confirm:true after showing the preview. Needs Stripe connected (Settings > Connectors > Stripe).

create_stripe_customer

ChatGPT
Create a Stripe customer by email (optional name, phone, description, metadata). If a customer with that email already exists it is returned instead and nothing is created — pass allowDuplicate:true to create a second one on purpose. LIVE key: confirm:true after showing the preview. Needs Stripe connected (Settings > Connectors > Stripe).

create_stripe_price

ChatGPT
Add a price to an existing Stripe product: amount in MAJOR units (19 or 19.00 is $19.00, never 1900), currency, optional recurring interval and intervalCount, optional nickname. Read back. LIVE key: confirm:true after showing the preview. Idempotent on its arguments. Needs Stripe connected (Settings > Connectors > Stripe).

create_stripe_product

ChatGPT
Create a product on the brand's Stripe account together with its default price: name, optional description, amount in MAJOR units — 19 or 19.00 both mean $19.00, and a zero-decimal currency like JPY is whole — a 3-letter currency, and an optional recurring interval (day, week, month, year) with intervalCount. Returns both ids, read back. On a LIVE key it shows what it is about to create and needs confirm:true; on a test key it just does it. Idempotent on its arguments. Needs Stripe connected (Settings > Connectors > Stripe).

create_tag_manager_tag

ChatGPT
INSTALL A TAG in the container's draft workspace: a Google Ads conversion tag, a GA4 event, or a third-party pixel. IT DOES NOT GO LIVE. Everything written here lands in a WORKSPACE, which is a draft, and it does not run on the website until a human opens Tag Manager and publishes the container. Staging NEVER publishes: making a tag live is a separate, explicitly confirmed call (publish_tag_manager_container), because publishing changes what executes for every visitor on the live site. Always tell the user the change is staged, and that it does nothing until it is published. TWO THINGS YOU MUST GET FROM THE CONTAINER RATHER THAN INVENT. (1) type is a Tag Manager type string and Google publishes no machine-readable list of them, so Hermoso forwards whatever you pass rather than validating against a list that would go stale and start refusing real types. Get the right one by calling list_tag_manager_tags and copying the type off a tag of the same kind that already exists, or read it in Tag Manager. (2) firingTriggerId is required and must name at least one real trigger id from list_tag_manager_tags, because a tag with no trigger is installed and never runs, which is worse than not installing it. Use create_tag_manager_trigger if the container has no suitable trigger. 0 credits. Needs Google Tag Manager connected.

create_tag_manager_trigger

ChatGPT
CREATE A TRIGGER, the rule that decides when a tag fires, in the container's draft workspace. Use it when list_tag_manager_tags shows no trigger matching what a conversion needs, for example a purchase confirmation page or a dataLayer purchase event. A trigger on its own fires nothing: it has to be named in a tag's firingTriggerId, so create the trigger, then create or update the tag that uses it. Like every other write here it lands in a DRAFT and does not reach the website until a human publishes the container. type must be one of Tag Manager's own trigger types, and an unrecognised one is refused by name with the accepted list, for free, before any request. A customEvent trigger with no customEventFilter fires on EVERY dataLayer event in the container rather than the one you meant, so that is refused too. 0 credits. Needs Google Tag Manager connected.

create_tiktok_ads_ad

ChatGPT
Create the ad itself — the creative that runs under an existing TikTok ad group. CREATED PAUSED with no override (TikTok’s own default is ENABLED); it spends nothing until set_tiktok_ads_status(confirm:true), and TikTok additionally reviews it before it can ever show. WHERE THE CREATIVE COMES FROM: run upload_tiktok_ads_creative on a finished Hermoso render (or any public https video) and pass back the videoId AND the coverImageId it returns as imageIds — a TikTok video ad needs BOTH, and TikTok rejects any cover whose dimensions differ from the video, so the video’s own cover is the only one that reliably fits. A videoId with no imageIds is refused here, for free. AN IDENTITY IS MANDATORY AND HAS NO DEFAULT: identityId AND identityType both come from list_tiktok_ads_identities and the ad appears publicly as that TikTok account, so let the USER pick and never guess — the call is refused outright without either. SPARK ADS — RUN A REAL ORGANIC POST INSTEAD: pass tiktokItemId (from list_tiktok_ads_identity_posts or list_tiktok_ads_spark_posts) INSTEAD OF videoId, and the ad IS that TikTok post, keeping its own comments, likes and shares under the account that made it. Spark needs an identityType of TT_USER, BC_AUTH_TT or AUTH_CODE — never CUSTOMIZED_USER, which is a Custom Identity and cannot carry one. THIS MATTERS BEYOND STYLE: TikTok is phasing Custom Identity out — ad accounts created on or after January 15, 2026 cannot create non-Spark ads at all, and existing accounts can no longer create them either, for any ad group delivering to Automatic or Select Placement with TikTok included (only Pangle / Global App Bundle campaigns are unaffected). Defaults: adFormat SINGLE_VIDEO, callToAction LEARN_MORE. THE STATUS IS READ BACK FROM TIKTOK’S OWN STORED ROW, never assumed: if it comes back anything other than DISABLE the note is a Warning: warning that the ad would serve the moment its campaign is enabled — print that verbatim and act on it before touching anything above it.

create_tiktok_ads_ad_group

ChatGPT
Create an ad group under an existing TikTok campaign — the tier that holds the budget, the bid, the placements, the schedule and ALL the targeting. CREATED PAUSED with no override (TikTok would otherwise create it ENABLED, and a live ad group under a paused campaign is exactly how spend escapes a one-level pause); it spends nothing until set_tiktok_ads_status(confirm:true). TIKTOK REQUIRES THIRTEEN FIELDS TOGETHER and its error names only the FIRST missing one per round trip, so the server checks the whole set locally and refuses with EVERY absentee at once — which matters here because QPS is 1 and each round trip costs a second. Sensible defaults are supplied for placement, pacing, promotion type, schedule type, optimization goal, billing event, bid type and budget mode, so the ONE thing you must resolve yourself is LOCATION: locationIds is effectively mandatory (TikTok: "‘location_ids’ or ‘zipcode_ids’ must be specified") and comes from search_tiktok_ads_targeting(kind:"location"). Resolve interest and language ids the same way — a guessed id either fails the create or targets the wrong people, silently. CONVERSION CAMPAIGNS (WEB_CONVERSIONS / CONVERSIONS / PRODUCT_SALES / LEAD_GENERATION) NEED A PIXEL: set optimizationGoal CONVERT, pass pixelId AND optimizationEvent, and billingEvent OCPM — TikTok pairs a billing event with every goal MANUALLY and derives nothing, so the server supplies the right one if you leave it out. Without a pixel TikTok refuses with "Please select a pixel." The result is READ BACK from TikTok, and the pixel and event it reports are the STORED ones.

create_tiktok_ads_audience

ChatGPT
Create a TikTok custom audience from what people actually did — the thing that turns TikTok targeting from interests-and-geo into retargeting. audienceType chooses the source and EACH ONE READS A DIFFERENT ID SPACE for eventSourceIds, which is the mistake to avoid: PIXEL takes pixel ids (list_tiktok_ads_pixels), APP takes app ids (list_tiktok_ads_apps), ENGAGEMENT takes AD GROUP ids, ENGAGEMENT_ORGANIC_VIDEO takes TikTok post ids (list_tiktok_ads_identity_posts, max 10), TIKTOK_SHOP takes shop ids, OFFLINE takes offline event-set ids — and a LEAD_GENERATION audience must carry NO eventSourceIds at all, because TikTok errors if the field is present. The event on each rule must belong to that audience type: an event from a different family is refused here by name rather than silently building an audience that measures the wrong thing. THE AUDIENCE IS NOT USABLE IMMEDIATELY — TikTok takes up to 48 hours to process it, and until then it cannot be attached to an ad group. Creating one spends nothing: an audience is a definition. The reply is READ BACK from TikTok, because TikTok’s create returns an id and nothing else.

create_tiktok_ads_catalog

ChatGPT
Create a product catalog inside a TikTok Business Center — the container Dynamic Product Ads sell from. REGION AND CURRENCY ARE SET ONCE AND CAN NEVER BE CHANGED: TikTok’s own catalog-update endpoint changes the NAME and nothing else, so a catalog created in the wrong region or currency has to be replaced. Confirm both with the user before calling, and use list_tiktok_ads_catalogs(regions:true) to see which regions this Business Center may use. The new catalog IS EMPTY — products arrive through a scheduled feed, so the next step is always manage_tiktok_ads_catalog_feed. ENTERTAINMENT and MINI_SERIES catalogs are allowlist-only at TikTok and are offered rather than refused, because an allowlisted advertiser can use them. Creating a catalog spends nothing.

create_tiktok_ads_crm_event_set

ChatGPT
Create a CRM Event set — the container for lead-lifecycle events, and the id send_tiktok_ads_events needs as eventSourceId when eventSource is "crm". Making one is how a LEAD_GENERATION campaign stops optimising toward form fills and starts optimising toward leads that qualify and close. Warning: TIKTOK PUBLISHES CREATE AND LIST FOR THESE AND NOTHING ELSE — no update and no delete anywhere in its API reference — so a set made here is PERMANENT, in the same way a TikTok pixel is, and an advertiser is capped at fifty with no API way to free a slot. Name it something the user will still recognise in a year. Free.

create_tiktok_ads_lookalike_audience

ChatGPT
Create a TikTok Lookalike Audience — TikTok finds people who resemble a seed audience you already have. The seed must exist, must hold AT LEAST 100 people, and cannot itself be a lookalike. audienceSize trades reach against similarity: NARROW is closest to the seed, BROAD reaches furthest. includeSource has NO DEFAULT and TikTok requires it — it decides whether the seed’s own people remain inside the new audience, which matters because including them double-targets people you are already reaching. LOOKALIKE LOCATIONS ARE A SHORTER LIST THAN ORDINARY AD TARGETING (39 countries), so a location id that works on an ad group is not automatically valid here; an unsupported one is refused by name with the full list rather than sent. Takes up to 48 hours to process, and cannot be attached to a Reach & Frequency ad group. Creating one spends nothing.

create_tiktok_ads_pixel

ChatGPT
Create a new TikTok pixel on an ad account so conversion campaigns have something to optimise toward. Returns the pixel id and the pixel CODE plus the script to install. A PIXEL ALONE IS NOT ENOUGH AND THIS IS THE PART PEOPLE MISS: it measures nothing until (1) the script is installed on the website and (2) the specific conversion EVENT is defined on it — until then TikTok refuses a conversion ad group with "This pixel event type does not exist." TikTok caps the name at 40 characters, rejects emojis and rejects DUPLICATE names, and it recommends naming the pixel after the site it measures. Creating a pixel spends nothing and serves nothing — it is a measurement definition. NOTE: TikTok publishes NO endpoint that deletes a pixel, so a pixel created here is permanent on that ad account.

create_tiktok_ads_rf_ad_group

ChatGPT
Create the ad group of a REACH & FREQUENCY campaign. THIS IS THE ONE OBJECT HERMOSO CREATES THAT CANNOT BE BORN PAUSED, and that is TikTok’s doing, not ours: /adgroup/rf/create/ publishes no operation_status anywhere in its parameter table, so there is no field to force — and an R&F ad group is a RESERVATION whose budget locks 5 minutes before delivery. So it needs confirm:true, and WITHOUT confirm NOTHING IS CREATED: you get the sentence describing exactly what would be booked, which you must show the user before asking for a yes. The three reservation numbers must come from tiktok_ads_rf_inventory_estimate — TikTok silently books its own maximum rather than refusing a number outside the estimated range. REACH & FREQUENCY IS ALLOWLIST-ONLY PER AD ACCOUNT: TikTok requires the advertiser to be allowlisted by a TikTok representative and to have signed a Commercial Contract for Branding, no API reports that status, and if TikTok refuses this the remedy is a conversation with the rep rather than a different field.

create_tiktok_ads_rule

ChatGPT
AUTOMATED RULES — a standing instruction TikTok runs on the user’s ad account on TIKTOK’S schedule, with nobody watching. WHAT A RULE CAN DO UNATTENDED, depending on its actions: pause a campaign / ad group / ad (TURN_OFF), email a report (MESSAGE), LOWER a daily budget, lifetime budget or bid (DECREASE) — or TURN AN OBJECT BACK ON and RAISE a budget or bid (TURN_ON, INCREASE, ADJUST_TO). The second group is standing permission to spend the user’s money without anyone present, on objects the rule re-selects at every execution, so treat it as such and say so before creating one. EVERY RULE IS CREATED TURNED OFF AND READ BACK TO PROVE IT: TikTok publishes no status field on its create and its own example shows a fresh rule running, so Hermoso creates it and disarms it in a second call — and if that second call fails the reply says LOUDLY that a live rule is on the account and how to switch it off. ARMING IS A SEPARATE ACT (set_tiktok_ads_rule_status), which is what makes the rule inspectable in between. An INCREASE or DECREASE must carry value.limit; TikTok requires it and it IS the safety rail — an increase without a ceiling ratchets upward every time the rule fires. TWO THINGS TIKTOK ITSELF PUBLISHES ABOUT THIS ENDPOINT: it is "supported exclusively for direct advertisers" and says SaaS platforms managing multiple advertisers cannot use it, so it may simply be refused for this account — that is THEIR restriction, not a broken connection and no reconnect changes it; and every notification it generates goes to the developer address registered on the app rather than to the user, so tiktok_ads_rule_results is the only place they will see what a rule did. Free.

create_tiktok_smart_ad

ChatGPT
Add the creative to a Smart+ ad group. Smart+ combines the materials you give it rather than running one fixed cut, so this is where the videos and copy go. Created PAUSED. If TikTok accepts the request but returns no ad id, the reply says nothing is confirmed to exist rather than claiming success — check Ads Manager before retrying, or a second call can create a twin.

create_tiktok_smart_ad_group

ChatGPT
Add an ad group to a Smart+ campaign — the level holding the targeting, budget, schedule and bidding. TikTok requires NINE fields together here and reports only the FIRST one it hits per request, so every absentee is named at once rather than one per round trip. Created PAUSED; it has no ads until you add one.

create_tiktok_tto_anchor

ChatGPT
Use this when the brand wants a webpage anchor for creator videos. PERMANENT: an anchor made here can never be deleted, so confirm with the user first. It shows only to viewers in countryCode, and the landing page must be a product or service detail page. Thumbnail exactly 210x375, under 2MB. Free.

create_tiktok_tto_brand_profile

ChatGPT
Create a Brand Profile on a TikTok One account; the identity creators see when they open an invite link. THE NAME, INDUSTRY AND LOGO ARE PERMANENT: TikTok marks all three "cannot be updated" and publishes no brand-profile update endpoint anywhere in its API, so a profile is authored exactly once and a typo has to be lived with. Confirm all three with the user before calling. The logo must be a 1:1 image under 5MB at a publicly reachable URL, and the website must start with https. Adding tiktokAccountUrl lets creators see the brand’s own TikTok account on the profile, which TikTok says improves how many of them take the invitation seriously. Free.

create_tiktok_tto_campaign

ChatGPT
Create a TikTok One creator campaign and get the invite link creators tag their videos with. TWO SHAPES, AND THEY NEED DIFFERENT FIELDS: campaignType "CAMPAIGN" invites named creators by handle and produces a campaign-level link; campaignType "BRAND_LINK" requires a brandProfileId, takes no handles at all, and produces one open link anyone can tag a video with. THE FIELD WITH LASTING CONSEQUENCES IS advertiserIds: naming ad accounts means every video a creator links to this campaign AUTOMATICALLY grants those accounts Spark Ads rights for sparkAdsAuthorizationDays and is synced into their creative library; that is standing permission rather than a one-off, and TikTok requires the days whenever advertiserIds is given. sendNotification puts an invitation in a real creator’s TikTok inbox and is OFF unless you ask for it; without it the creators see the invitation only if you share the link yourself. Handles TikTok rejects come back in a failed list rather than as an error, so the reply reports which ones were ACTUALLY invited rather than which ones were asked for. Free.

create_whatsapp_template

ChatGPT
Submit a new WhatsApp message template for Meta’s review. This is the ONLY way a business can start a WhatsApp conversation: outside the 24-hour window that opens when a customer messages first, WhatsApp accepts approved templates and nothing else. CATEGORY IS NOT COSMETIC — MARKETING (anything promotional), UTILITY (a transactional follow-up to something the customer did) or AUTHENTICATION (a one-time passcode) decides both what Meta charges for the conversation and the policy the content is reviewed against, and Meta RE-CATEGORISES a template whose content does not match the claim; the reply reports the category Meta actually assigned, not the one requested. The name is lowercase letters, digits and underscores only. Language is never defaulted: en and en_US are different templates to Meta. Submitting does NOT make it sendable — it comes back PENDING and has to be re-read with list_whatsapp_templates. CAROUSEL TEMPLATES ARE SUPPORTED: add a “CAROUSEL” component whose “cards” array holds 2–10 cards, each card carrying its own HEADER (format IMAGE or VIDEO for a media carousel, PRODUCT for a catalog one), an optional card BODY of at most 160 characters — a different and far shorter limit than the message bubble’s 1024 — and up to 2 buttons. Meta requires every card to carry THE SAME components, and if any card has body text then all of them must. At creation a card is only its “components” array; “card_index” belongs to the send payload, not to this one. A MEDIA HEADER NEEDS AN UPLOADED ASSET: any IMAGE, VIDEO or DOCUMENT header — including every card header in a media carousel — must carry example: { header_handle: [ … ] } holding a handle from Meta’s Resumable Upload API, or Meta refuses the whole template with a bare “Invalid parameter”. Hermoso does not upload template media yet, so it refuses that locally and by name instead. A TEXT header needs no handle, and a PRODUCT card carousel takes its pictures from a connected catalog. 0 credits.

create_x_ads_campaign

ChatGPT
Create a campaign on X (Twitter). ALWAYS CREATED PAUSED with no override — it spends NOTHING until set_x_ads_status(confirm:true). A CAMPAIGN ALONE CANNOT SERVE ON X: it needs a line item and a promoted post underneath it, and the read-back says so rather than letting you call it a finished ad. Requires a funding instrument (a payment method on the X ad account) — omit fundingInstrumentId to be shown the usable ones, and if there are none this refuses with that reason instead of failing at X. Budgets are in the ad account’s own currency. Everything is READ BACK from X before you are told it exists; print the returned note verbatim.

create_x_ads_line_item

ChatGPT
Create a line item — X's ad group — under a campaign. ALWAYS CREATED PAUSED with no override. THE TREE ON X IS campaign -> line item -> promoted post, and a campaign ALONE CANNOT SERVE: this is the middle level, and it still cannot serve until you attach a post with create_x_ads_promoted_tweet. Targeting attaches HERE (add_x_ads_targeting), never to the campaign. objective is validated before dispatch because X answers an invalid one with a 500 that reads like an outage; note that WEBSITE_CONVERSIONS and SITE_VISITS are goal values and are NOT objectives. bidStrategy MAX/TARGET require a bidAmount; AUTO lets X set it. Omitting startTime records now, and the read-back says so. Everything is READ BACK from X before you are told it exists; print the returned note verbatim.

create_x_ads_promoted_tweet

ChatGPT
Attach an existing X post to a line item so the line item has a creative — the LAST piece of the tree, and without it nothing can ever serve however the statuses are set. The tweet id is the number at the end of the post URL (x.com/<handle>/status/<id>), not the URL. Several ids may be attached at once; X creates one promoted-tweet row per post. ATTACHMENT IS VERIFIED by re-reading the line item's own promoted posts rather than by trusting X's echo, and if that read cannot run the note says UNCONFIRMED instead of claiming success. Everything Hermoso built above this is PAUSED, so attaching a post starts no spend. Print the returned note verbatim.

create_youtube_channel_section

ChatGPT
Use this when the brand wants a new shelf on its channel homepage. Public immediately. singlePlaylist and multiplePlaylists take playlist ids, multipleChannels takes channel ids; only multiplePlaylists and multipleChannels take a title. Free.

create_youtube_playlist

ChatGPT
Use this when the brand wants a new playlist on its YouTube channel. It is PRIVATE unless privacy says otherwise. Add videos with add_video_to_youtube_playlist. Free.

delete_analytics_key_event

ChatGPT
REMOVE A KEY EVENT — the reverse of create_analytics_key_event, and note the ASYMMETRY with custom dimensions: a key event really can be DELETED, where a custom dimension can only be archived. Nothing is destroyed — GA4 keeps collecting the underlying event and keeps all of its history, this only stops it counting as a conversion, and it can be marked again at any time. Use it for an event marked as a conversion by mistake, or one that should no longer be optimised toward. BE AWARE IT REACHES FURTHER THAN GA4: anything importing this conversion — Google Ads smart bidding in particular — stops receiving it, which changes how campaigns bid. Called without confirm it deletes nothing and describes the key event back to you. Needs edit access on the property.

delete_apple_ads_object

ChatGPT
Delete an Apple Ads campaign, ad group, keyword, negative keyword, ad, creative, asset, location group or budget order. Requires confirm:true, and optionally confirmName echoed back to prove you aimed at the right object. THIS CASCADES AND CANNOT BE UNDONE: Apple soft-deletes with no undelete, and deleting a campaign takes every ad group, keyword and ad underneath it. THE BLAST RADIUS DIFFERS BY LEVEL and the refusal states the right one for the object you named: deleting a CREATIVE does not delete the ads that use it — it makes every one of them stop serving, permanently, with no way back but a new creative; deleting an ASSET makes any Apple Maps creative using it INVALID; deleting an AD leaves its creative untouched and available; deleting a LOCATION GROUP is permanent with no restore, and every ad group targeting it loses that constraint immediately — an ad group left targeting no location group STOPS serving rather than reaching everywhere. In almost every case set_apple_ads_status(status:"PAUSED") is what you actually want — it stops all spend and is reversible — but a creative, an asset, a location group and a budget order have NO status, so for those the reversible move is pausing whatever references them. DELETING A BUDGET ORDER CASCADES TO NOTHING — it is a spend ceiling, not a parent — but every campaign assigned to it loses that ceiling, and Apple REFUSES the delete outright while any campaign is still assigned, or once the order has started, or if it is cancelled, completed or exhausted; the refusal names whichever applies before anything is sent. The reply is confirmed by RE-READING the object: for a delete, an absent or deleted-flagged row is the proof, never the HTTP 200. Free.

delete_bluesky_dm_for_self

ChatGPT
Use this when one Bluesky direct message should disappear from THIS account’s view. The other person still sees it; Bluesky has no delete-for-everyone. Get messageId from read_bluesky_dm. Free.

delete_bluesky_post

ChatGPT
PERMANENTLY delete one of the connected Bluesky account's OWN posts. IRREVERSIBLE — the AT Protocol removes the record from the account's repo, there is no trash and no undelete, and the post's likes, reposts, replies and quotes go with it. Call it WITHOUT confirm first: nothing is deleted, and it reports the post's REAL text and its live like / repost / reply / quote counts read back from Bluesky. Show the user that, get an unambiguous yes, then call again with confirm:true — plus, once the post has ANY engagement, confirmText echoing the post's own text (the first 40 characters is enough; any longer leading run works too). confirmText exists because confirming that you meant to delete SOMETHING does not prove you aimed at the right post, and a wrong id must not be confirmable blind. A brand-new post with nothing on it stays a ONE-call delete. Identify the post by its AT-URI or by just its RECORD KEY — the short id at the end of its bsky.app link, e.g. 3mtc4n3fibn2x. Deleting only ever works on the connected account's own posts; another account's URI is refused. 0 credits. Needs Bluesky connected (Settings > Connectors > Bluesky).

delete_creator

ChatGPT
Remove a saved creator from this workspace’s cast by id (from list_creators). Records a cross-device delete so they don’t reappear on the user’s other devices. It only drops the roster entry — ads already rendered with that person are untouched — and the same portrait can be saved again with save_creator, so no confirm is needed.

delete_dm_automation

ChatGPT
Delete one automation by id on any channel. Nothing further is sent by it; messages already sent stay; on X the event subscription is removed when no rule needs it. To pause instead, save it with enabled:false. Free.

delete_drive_file

ChatGPT
Delete a Drive file. By default it goes to Trash (recoverable); pass permanent:true to delete it forever. Pass fileId (from list_drive_files) + confirm:true. Irreversible when permanent — confirm with the user first.

delete_google_ads_object

ChatGPT
PERMANENTLY remove a Google Ads object. Google has no HTTP delete — removal is a remove operation that puts the object in the terminal REMOVED state, which cannot be undone or re-enabled, so treat it as a delete. Levels: "campaign" + campaignId · "adGroup" + adGroupId · "ad" + adGroupId AND adId · "keyword" + adGroupId AND keywordId · "conversionAction" + conversionActionId · "campaignAsset"/"adGroupAsset" + the LINK’s full resourceName (get it from google_ads_report over campaign_asset / ad_group_asset — an asset id alone does not identify a link). THERE IS DELIBERATELY NO "asset" LEVEL: Google publishes no operation that deletes an Asset, only its links, so removing a link unlinks the asset and leaves it in the library. CALL IT WITHOUT confirm FIRST — nothing is removed and you get the object’s real name, status, LIFETIME SPEND and child counts read live from Google; show the user exactly that. A target with children, live delivery or real spend additionally needs confirmName (its exact name) and confirmChildren (the child count from that read-back). Removing a CAMPAIGN also removes its campaign-owned budget, and the note says whether it did. Removing the last ENABLED conversion action makes every smart-bidding campaign on the account undeliverable — the refusal says so. To stop delivery without removing, use set_google_ads_status(status:"PAUSED").

delete_google_business_post

ChatGPT
Remove a Post from the brand’s Google Business Profile listing. This takes it off Google Search and Maps immediately and CANNOT be undone — confirm with the user first. Pass the full post name from list_google_business_posts. Needs Google Business Profile connected.

delete_instagram_dm_automation

ChatGPT
Delete one automation by id. Nothing further is sent by it; DMs already sent stay. To pause instead, save it with enabled:false. Free.

delete_linkedin_ads_object

ChatGPT
Delete a LinkedIn campaign group, campaign or creative (level:"creative" + creativeId). LinkedIn HARD-deletes only DRAFT objects; anything that has ever run is moved to PENDING_DELETION instead — it stops serving and its reporting history is retained. The returned note says which of the two actually happened, and you must repeat that rather than claiming a clean delete. Irreversible either way, so it requires confirm:true.

delete_linkedin_comment

ChatGPT
Remove a comment from one of your LinkedIn company Page's posts — your own Page's comment, or somebody else's on your Page. Community Management surface, same separate connection. CONFIRM-GATED: call once without confirm to be refused and shown what would go, then again with confirm:true. Deleting is immediate and public. organizationId is required because LinkedIn refuses a Page-level delete that does not name the acting organization. The delete is idempotent, so a repeat reports success rather than an error.

delete_linkedin_lead_subscription

ChatGPT
Remove a lead notification webhook (subscriptionId from list_linkedin_lead_subscriptions). Leads themselves are unaffected and stay readable; only the real-time delivery stops. Read back from LinkedIn. Free.

delete_merchant_data_source

ChatGPT
Delete a data source (feed) from a Merchant Center account. CONFIRM-GATED and the heavier of the two deletes: without confirm:true nothing is deleted and it reports the feed plus HOW MANY PRODUCTS would be destroyed with it, counted live from Google. This is NOT recoverable the way a product delete is — the products would have to be re-inserted into a new feed, and feedLabel/contentLanguage are immutable so a replacement may not be identical. Free.

delete_merchant_product

ChatGPT
Delete a product from a Merchant Center feed. CONFIRM-GATED: without confirm:true nothing is deleted and it reports the real product it WOULD delete — title, price and availability read back from Google — because the id you were given proves nothing about what is actually there. It is recoverable: re-inserting the same offerId re-creates the product. Needs the same offerId + contentLanguage + feedLabel that identify the product (contentLanguage and feedLabel come from the feed when it declares them). Free.

delete_messenger_marketing_campaign

ChatGPT
Delete a marketing-message campaign. Irreversible: the first call names it and refuses; confirm:true deletes.

delete_meta_audience

ChatGPT
PERMANENTLY delete a Meta custom audience or lookalike. Meta’s own warning: "When you delete a custom audience, it will be permanently removed from your account and your ads using it will stop running." An audience is the one ad object whose value is its CONTENTS — a big retargeting list cannot be rebuilt, it has to re-accumulate — so CALL IT WITHOUT confirm FIRST: nothing is deleted, and you get its real name, how many people are in it and which lookalikes were built from it, read live from Meta. Show the user exactly that. A populated audience, or one with lookalikes, then needs confirmName set to its exact name (and confirmChildren set to the lookalike count when there are any). META REFUSES to delete an audience that has lookalikes derived from it (error 2656) — delete those first; the unconfirmed call names them. Pass adAccountId + audienceId (from list_meta_audiences). The result is READ BACK from Meta: it says deleted only when the id no longer resolves.

delete_meta_catalog

ChatGPT
PERMANENTLY delete a product catalog. Meta does not let a catalog be removed from a business portfolio and then restored, and it cannot be transferred to another portfolio — there is no archive and no undo, and its product sets go with it, so any ad set still bound to one of those sets keeps spending and stops having products to show. Confirming is therefore not enough on a catalog that holds anything: pass confirm:true PLUS confirmName set to the catalog’s exact name (and confirmChildren when asked), which proves you inspected the catalog you are aiming at rather than merely meaning to delete something. An EMPTY catalog stays a one-call delete. Call meta_catalog_blast_radius first to get those numbers and to show the user what they are agreeing to. The result is the READ-BACK, never Meta’s acknowledgement — if the catalog still resolves afterwards you are told it is NOT confirmed. Free, 0 credits.

delete_meta_comment

ChatGPT
Use this when a comment on the brand’s own Facebook or Instagram post must be removed for good. PERMANENT: pass confirm:true only after the user agreed; hide_meta_comment is the reversible option. Free.

delete_meta_object

ChatGPT
PERMANENTLY delete a campaign, ad set, or ad. Pass objectId (from list_meta_ads) + adAccountId. DELETING A CAMPAIGN ALSO DELETES EVERY AD SET AND AD UNDER IT, and deleting an ad set deletes its ads — one id, the whole tree. Call it WITHOUT confirm first: it reports what the object is, its name, and how many children go with it. Show the user that, get an unambiguous yes, then call again with confirm:true plus confirmChildren set to the number it reported (only needed when there is at least one child). To stop delivery without deleting anything, use update_meta_object(status:"PAUSED") instead — that is reversible and this is not.

delete_meta_post

ChatGPT
Use this when a post the brand published on Facebook, Instagram or Threads must be removed. PERMANENT. For Instagram call it first WITHOUT confirm: nothing is deleted and the reply shows the post’s caption, likes, comments and carousel cards; show the user that, then call again with confirm:true (plus confirmName / confirmChildren when the reply asks). An Instagram carousel is deleted as a whole album. Get postId from list_meta_posts or list_instagram_media. Free.

delete_microsoft_ads_object

ChatGPT
PERMANENTLY delete a Microsoft Advertising campaign, ad group, ad or keyword. This is a real delete — Microsoft removes the object and it stops being returned by every read, with no undelete and no documented recovery window. Pass level:"campaign" + campaignId, level:"adGroup" + adGroupId, level:"ad" + adGroupId AND adId, or level:"keyword" + adGroupId AND keywordId. CALL IT WITHOUT confirm FIRST: nothing is deleted, and you get back the object’s real name, its status and how many ad groups / ads / keywords go with it, read live from Microsoft — show the user exactly that. If the object has children, is Active, or has spent, confirming alone is NOT enough: you must also pass confirmName set to its exact name and confirmChildren set to the child count from that read-back, which is what proves you are deleting the object you think you are. A campaign that is paused, empty and never ran deletes on plain confirm:true. To stop delivery WITHOUT deleting, use set_microsoft_ads_status(status:"Paused") instead. The result is READ BACK from Microsoft: it says "deleted" only when the object no longer resolves, "not confirmed" if it does, and "could not tell" if the check itself failed — repeat that verbatim rather than claiming success.

delete_microsoft_merchant_product

ChatGPT
Delete one product offer from a Microsoft Merchant Center store. Pass productId as the FULLY QUALIFIED id (channel:contentLanguage:targetCountry:offerId), not the offerId. Call without confirm first — nothing is deleted and you get Microsoft's own better advice back: deleted products can take UP TO 12 HOURS to stop delivering, so if the goal is to stop showing it today, upsert it with availability 'out of stock' instead. The outcome is READ BACK by re-fetching the offer, and says 'deleted', 'not confirmed' or 'could not tell' — repeat that verbatim rather than claiming success from the delete's own empty response. 0 credits.

delete_onedrive_file

ChatGPT
Delete a OneDrive item — it moves to the OneDrive recycle bin (recoverable there). Pass fileId (from list_onedrive_files) + confirm:true. Confirm the exact file with the user first.

delete_openai_ads_lead_sync

ChatGPT
Delete the lead-sync subscription: every lead form on the account stops delivering leads. confirm:true required.

delete_openai_ads_object

ChatGPT
Retire a ChatGPT Ads campaign, ad group or ad. THE OPENAI ADVERTISER API HAS NO DELETE — archiving is its only teardown, and OpenAI’s own guidance is "only archive objects you have no further use for, as archiving isn’t reversible": there is no un-archive, not even through support. So say ARCHIVED, never "deleted". Pass level:"campaign" + campaignId, level:"adGroup" + adGroupId, or level:"ad" + adId. CALL IT WITHOUT confirm FIRST — nothing is archived and you get the object’s real name, status and child count read live from OpenAI; show the user exactly that. A target with children or live delivery additionally needs confirmName (its exact name) and confirmChildren (the count from that read-back). PAUSING stops all spend and keeps the object editable — offer that first whenever the user only wants delivery to stop. Archiving a campaign is not documented to cascade, so archive the children yourself if they should stop too. The result is READ BACK: it says archived only when OpenAI reports the archived status.

delete_openai_ads_spend_window

ChatGPT
Delete an active or scheduled spend-limit window before it ends — this REMOVES a spending ceiling. confirm:true required.

delete_pinterest_ads_object

ChatGPT
Retire a Pinterest campaign, ad group or ad. PINTEREST API v5 HAS NO DELETE for any of the three — ARCHIVED is its terminal state, and Pinterest’s own campaign docs call an archived campaign "deleted" and say reversing it means filing a ticket with their customer ops team, so there is no un-archive you or the user can call. Say ARCHIVED, not "deleted": the object stays on the account with its reporting history and simply drops out of the default list view. Pass level:"campaign" + campaignId, level:"adGroup" + adGroupId, or level:"ad" + adId. CALL IT WITHOUT confirm FIRST — nothing is archived and you get the object’s real name, status and child count read live from Pinterest; show the user exactly that. A target with children or live delivery additionally needs confirmName (its exact name) and confirmChildren (the count from that read-back). PAUSING is fully reversible — offer it first whenever the user only wants delivery to stop. Pinterest documents no cascade, so archive the children yourself if they should stop too.

delete_pinterest_pin

ChatGPT
PERMANENTLY delete a Pin. Pinterest has no undelete and no archive for one. Call it WITHOUT confirm first: nothing is deleted, and it answers with the Pin’s real title, its lifetime saves and impressions, and whether it HAS BEEN PROMOTED in an ad — all read back from Pinterest. Show the user that, get an unambiguous yes, then call again with confirm:true plus confirmName (its exact title) once it has saves or has been promoted, because confirming that you meant to delete SOMETHING does not prove you aimed at the right Pin. DELETING A PIN THAT AN AD PROMOTES pulls the creative out from under that ad, so check the promoted flag before agreeing. The verdict is read back from Pinterest, never taken from its 2xx. 0 credits. Needs Pinterest connected.

delete_playbook

ChatGPT
Delete a saved playbook by id (from list_playbooks). Records a cross-device delete so it does not come back on the next sync. Minor + re-creatable, so no confirm needed.

delete_reddit_ads_asset

ChatGPT
Permanently delete a creative asset from the Reddit asset library. Without confirm:true it deletes nothing and reports what the asset is; with it, the reply is the read-back (the asset reads as DELETED or gone), never Reddit’s 200. Free.

delete_reddit_ads_audience

ChatGPT
Permanently delete a Reddit custom audience. This is one of the very few things Reddit really deletes — campaigns, ad groups and ads are only ever archived — and it cannot be undone: the uploaded list is gone and any ad group targeting it loses that audience. Confirm-gated: show the user the audience name and its size, get an explicit yes, then call again with confirm:true.

delete_reddit_ads_object

ChatGPT
Remove a Reddit campaign, ad group or ad. REDDIT HAS NO DELETE VERB for any of the three — its whole Ads API has exactly four HTTP DELETE endpoints and none of them is a campaign, ad group or ad — so removal is the configured_status field: DELETED (permanent) or ARCHIVED (out of service, and can be set back to PAUSED). DELETED is additionally TIME-GATED: Reddit refuses to delete anything modified in the last 3 hours, and the refusal names ARCHIVED as the immediate alternative. CALL IT WITHOUT confirm FIRST — nothing changes, and you get the object’s real name, status and how many ad groups / ads sit under it, read live from Reddit. A target with children or live delivery then needs confirmName (its exact name) and confirmChildren (the count from that read-back). Reddit does NOT document whether removing a campaign cascades to its ad groups and ads — each carries its own status — so remove the children yourself if they should go too. To stop delivery without removing, use set_reddit_ads_status(status:"PAUSED").

delete_reddit_ads_saved_audience

ChatGPT
Delete a Reddit saved audience — the named, reusable targeting block ad groups point at. Reddit publishes NO delete verb for one (its whole Ads API has four, and this is not among them), so removal is its status field set to DELETED. THE BLAST RADIUS IS THE AD GROUPS USING IT: every live ad group pointing at this audience loses that targeting definition the moment it goes, and Reddit’s own active_ad_groups_count is what says how many. CALL IT WITHOUT confirm FIRST — nothing is deleted and you get its real name and that count; show the user exactly that, and if any ad group uses it you must then pass confirmName (its exact name) and confirmChildren (the count). The result is READ BACK from Reddit.

delete_search_console_site

ChatGPT
Remove a property from the connected Google account. THIS LOSES HISTORY: the account loses access to that property's stored Search Console performance data, and re-adding it later means verifying ownership from scratch — Google does NOT restore the 16-month window on re-add. Nothing about the site itself, its pages or its ranking changes. CONFIRM-GATED: the unconfirmed call removes nothing and describes what would be lost, including how many sitemaps this account can currently see on it. The property is also dropped from this brand's shared list, so it cannot linger as a tick pointing at something unreachable. 0 credits.

delete_search_console_sitemap

ChatGPT
Remove a sitemap from a property. THIS DOES NOT REMOVE THOSE PAGES FROM GOOGLE'S INDEX — it stops Google using that file to discover new ones, and it can be submitted again at any time, so it is reversible. CONFIRM-GATED: called without confirm it removes nothing and reports what the sitemap actually IS, read back from Google (its error and warning counts and how many URLs it covers), so you can check you are aiming at the right one. Re-run with confirm:true to proceed. 0 credits.

delete_skill

ChatGPT
Delete one of the workspace’s CUSTOM skills by id (from list_skills). Built-in skills/recipes can’t be deleted. Minor + re-creatable, so no confirm needed.

delete_telegram_message

ChatGPT
PERMANENTLY delete one message the bot posted to a Telegram chat. Call it WITHOUT confirm first: nothing is deleted and you get a sentence to show the user. There is deliberately NO preview of the message — the Bot API has no method that reads one message back, so anything shown would be invented, and for the same reason the result after deleting is Telegram’s own success answer rather than a verified read-back. TWO VENDOR LIMITS, both Telegram’s and neither ours: "A message can only be deleted if it was sent less than 48 hours ago", and in a CHANNEL the bot needs the Post Messages right to remove even its own posts. Takes the same chatId as post_to_telegram plus the messageId post_to_telegram returned. 0 credits. Needs Telegram connected (Settings > Connectors > Telegram).

delete_thread

ChatGPT
PERMANENTLY delete one of the brand’s Threads posts. IRREVERSIBLE — Threads has no undelete. Call it WITHOUT confirm first: nothing is deleted, and it answers with the post’s real text plus its views, likes, replies and reposts read back from Threads. Show the user that, get an unambiguous yes, then call again with confirm:true — plus confirmName (the post’s exact text as it was reported) once anyone has engaged with it. confirmName exists because confirming that you meant to delete SOMETHING does not prove you aimed at the right post, and a wrong id must not be confirmable blind. Threads allows only 100 deletions per account per rolling 24 hours; threads_publishing_limit says how many are left, and a quota refusal otherwise reads like a broken connection. Note Meta documents nothing about what a delete does to the replies underneath a post, so do not promise the conversation survives. 0 credits.

delete_tiktok_ads_audience

ChatGPT
Delete one or more TikTok custom audiences. IRREVERSIBLE: TikTok publishes no undelete, any Lookalike seeded from one loses its seed, every ad group targeting it stops using it, and re-creating one means rebuilding the rule and waiting up to 48 hours for it to process again. WITHOUT confirm NOTHING IS DELETED — you get each audience’s real name, type and matched-user count READ BACK FROM TIKTOK, which is what you show the user before asking for a yes, because a confirm flag proves you meant to delete something and cannot tell an empty scratch audience from the one every campaign targets. Deleting MORE THAN ONE additionally needs confirmCount set to the exact number. If the audiences cannot be READ, the delete is refused outright rather than performed blind — an unknown blast radius on an irreversible action is not the same as an empty one.

delete_tiktok_ads_comment

ChatGPT
Warning: READ THIS BEFORE REACHING FOR IT: TikTok’s comment delete removes a comment YOUR OWN advertiser identity posted — your own reply. It CANNOT remove a comment left by a member of the public, and TikTok reports that per comment as canDelete on every row from list_tiktok_ads_comments. To take somebody else’s comment out of public view, use moderate_tiktok_ads_comment with operation HIDDEN, which is the moderation tool and is reversible. IRREVERSIBLE: TikTok publishes no undelete, the replies under it go with it, and re-posting means a new comment with a new id at the bottom of the thread rather than the one people answered. WITHOUT confirm NOTHING IS DELETED — you get the comment’s real text, author and date READ BACK FROM TIKTOK, which is what you show the user before asking for a yes. Deleting then additionally needs confirmText set to that exact text, because a confirm flag proves you meant to delete something and cannot prove you aimed at the right comment. If the comment cannot be READ, the delete is refused outright rather than performed blind.

delete_tiktok_ads_object

ChatGPT
Remove TikTok campaigns, ad groups or ads. TIKTOK HAS NO DELETE VERB — removal is modelled as a STATUS, exactly like Reddit — so this posts to the same route as set_tiktok_ads_status with status:"DELETE" rather than being a second implementation with its own rules. It is permanent, TikTok publishes no undelete, and it needs confirm:true; calling it WITHOUT confirm changes nothing and returns the sentence naming how many objects go, which is what you show the user first. TikTok does NOT document whether removing a campaign takes its ad groups and ads with it — each carries its own status — so remove the children yourself if they should go too, and VERIFY with list_tiktok_ads_campaigns afterwards rather than assuming a cascade either way. TO STOP DELIVERY WITHOUT DESTROYING ANYTHING, use set_tiktok_ads_status(status:"DISABLE"): that is reversible and this is not.

delete_tiktok_comment

ChatGPT
Use this when a comment the brand’s own account wrote must be removed for good. Other people’s comments can only be hidden (hide_tiktok_comment). PERMANENT: needs confirm:true. Free.

delete_tiktok_post_ad_authorization

ChatGPT
Delete the Spark-Ads authorization code for a post the authorized account owns. CONFIRM-GATED, and not because it spends: ads already built on the post keep running, but no NEW ad can be built against it until a fresh code exists, and TikTok REFUSES this outright while any campaign or ad using the post is still ACTIVE — pause those first. Calling it without confirm changes nothing and returns the sentence describing exactly what would happen. THE READ-BACK INVERTS: proof of success is that TikTok no longer reports a code for the post, and a code that is still there is reported as not confirmed rather than done. NEEDS THE TIKTOK ACCOUNT AUTHORIZATION (see tiktok_account_status).

delete_tiktok_tto_anchor

ChatGPT
Use this when a DRAFT anchor should be removed. TikTok deletes only drafts, so an anchor already in review or live cannot be removed. Free.

delete_vercel_deployment

ChatGPT
PERMANENTLY delete one Vercel deployment: its URL stops working and Vercel has no undo. Without confirm:true nothing is deleted and you get the deployment it would delete (URL, target, state) to show the user; a production deployment the domains still serve takes the site down. Call again with confirm:true only after the user says yes to that exact deployment. Free. Needs Vercel connected (Settings > Connectors > Vercel).

delete_whatsapp_template

ChatGPT
PERMANENTLY delete a WhatsApp message template. META DELETES EVERY LANGUAGE OF A NAME AT ONCE, which is a wider blast radius than most callers picture, so call it WITHOUT confirm first: nothing is deleted and it reports exactly how many templates and which languages would go. Then show the user that, get an unambiguous yes, and call again with confirm:true AND confirmName set to the template’s exact name — confirming that you meant to delete SOMETHING does not prove you aimed at the right template. Any message sent against a deleted template afterwards is refused. The answer is a RE-READ from Meta, never the 200: if the template still comes back it is reported as not deleted. 0 credits.

delete_x_ads_object

ChatGPT
PERMANENTLY delete an X campaign, line item, promoted post or targeting criterion. X CASCADES AND PUBLISHES NO UNDO: deleting a campaign destroys its line items and their promoted posts too — verified live, the children answer 404 the moment the parent is deleted. RUN IT WITHOUT confirm FIRST: that deletes nothing and reports the REAL blast radius read back off X (what is underneath it, whether it is live, whether it has spent). Show the user exactly that, get an unambiguous yes, then call again with confirm:true — and, for anything with children / live delivery / real spend, also confirmName set to its exact name and confirmChildren set to the real count, because confirm:true alone proves you meant to delete SOMETHING and cannot prove you aimed at the right object. TO STOP DELIVERY WITHOUT DESTROYING ANYTHING use set_x_ads_status PAUSED, which is reversible — except for a promoted post, which cannot be paused on X at all, so pause its LINE ITEM instead. Removing a targeting criterion WIDENS the audience rather than narrowing it. The result is confirmed by re-reading the object, never by X's 200.

delete_x_chat

ChatGPT
Use this when a conversation should leave the brand’s X inbox for good; the other people keep theirs. Needs confirm:true. Needs X connected; X bills per call, so it costs credits.

delete_x_post

ChatGPT
Permanently delete one of the connected account’s posts on X. This CANNOT be undone — confirm the exact post with the user first. Costs credits (X bills per API call). Needs X connected.

delete_youtube_caption

ChatGPT
Use this when a caption track should be removed. Viewers lose it immediately. Needs confirm:true. Tracks YouTube generated itself cannot be deleted. Get captionId from list_youtube_captions. Free.

delete_youtube_channel_section

ChatGPT
Use this when a homepage shelf should be removed. There is no undo; the layout would have to be rebuilt. Needs confirm:true. Get sectionId from list_youtube_channel_sections. Free.

delete_youtube_comment

ChatGPT
Use this when a comment must be removed for good; YouTube has no undelete. Needs confirm:true. To only take it out of public view use reject_youtube_comment. Free.

delete_youtube_playlist

ChatGPT
Use this when a playlist must be removed for good. The videos stay; the playlist, its order and every link to it go. Needs confirm:true, and for a public or non-empty playlist confirmTitle with its exact title. Free.

delete_youtube_playlist_image

ChatGPT
Use this when a playlist’s custom cover should be removed, so YouTube shows its first video’s thumbnail again. Get imageId from list_youtube_playlist_images. Free.

delete_youtube_report_job

ChatGPT
Stop a YouTube bulk reporting job. IRREVERSIBLE IN A WAY THAT IS EASY TO MISS: the job IS the history — deleting it discards every daily CSV it has accumulated, and a replacement job starts over with only a 30-day backfill, so anything older than that is gone for good. Call WITHOUT confirm first: nothing is deleted and you get the real job read back from YouTube (its report type and when it was scheduled) to show the user. Then call again with confirm:true. Needs a connected YouTube channel.

delete_youtube_video

ChatGPT
PERMANENTLY delete a video from the connected YouTube channel. IRREVERSIBLE — YouTube has no trash and no undelete, and the video's views, comments and every link or embed pointing at it go with it. Call it WITHOUT confirm first: nothing is deleted, and it reports the video's REAL title, privacy, view count and comment count read back from YouTube. Show the user that, get an unambiguous yes, then call again with confirm:true — plus, once the video is public or has any views or comments, confirmTitle set to its exact title. confirmTitle exists because confirming that you meant to delete SOMETHING does not prove you aimed at the right video, and a wrong id must not be confirmable blind. If the user only wants it out of public view, use update_youtube_video(privacy:"private") instead — that is reversible and this is not. Get the videoId from list_youtube_videos. 0 credits. Needs a connected YouTube channel.

deploy_vercel_page

ChatGPT
Publish a STATIC landing page to the brand's Vercel account from HTML (it becomes index.html) or a few files [{file, data, encoding:'utf-8'|'base64'}], up to 50 files and 4 MB. project names the Vercel project (created if it does not exist; it becomes part of the URL). target 'preview' (default) makes a new preview URL and leaves the live site alone; 'production' REPLACES what the project's domains serve. WITHOUT confirm:true nothing is sent and you get the exact preview to show the user; call again with confirm:true only after they say yes. The answer is Vercel's own deployment row (URL and state); a new deployment is QUEUED or BUILDING for a moment, so read it back with list_vercel_deployments deploymentId until READY. Free. Needs Vercel connected (Settings > Connectors > Vercel).

diagnose_posts

ChatGPT
WHAT TO FIX NEXT, post by post — and the tool that fixes it. post_performance tells you which hook is AHEAD; this tells you what is WRONG with a given post and where the next edit goes. Under-distributed is A HOOK PROBLEM (change the opening: mine_angles, then list_hooks, then plan_variations). Seen but not held is A RETENTION PROBLEM (plan_variations to rebuild the middle against the same hook). Seen, held, and still not converting is AN OFFER PROBLEM. FOUR REFUSALS, AND YOU SHOULD REPEAT THEM RATHER THAN PAPER OVER THEM: (1) a post younger than ~24h is TOO EARLY and is never called a failure — it has not had its run; (2) a metric the platform does not publish is UNMEASURED, never zero — Facebook has published no post reach since 2026-06-15, Reddit publishes no impressions, and Google Business publishes nothing per-post at all; (3) below 5 measured posts on a channel there is no baseline of the brand's own, and the ONLY fallback is a published short-video hook floor that is NOT our measured number and does not transfer off TikTok/Instagram/YouTube — it is attributed in the output and you should attribute it too; (4) it does not always find a problem, and 'nothing here needs fixing' is a real answer rather than a failure to look. Hermoso cannot see conversions for an organic post — no channel reports installs or purchases against a post id — so the offer rung runs ONLY when the user tells you they are not converting and you pass converting:false. Print summary verbatim. Read-only, 0 credits.

disable_tiktok_brand_hashtag

ChatGPT
Use this when TikTok should stop counting one hashtag as the brand’s. Exactly one per call, and not within 7 days of enabling it (the reply names the wait). Free.

disconnect_connector

ChatGPT
Disconnect a third-party account from this workspace (Meta, Google Ads, Google Drive/Sheets/Docs, YouTube, TikTok, LinkedIn, X, Reddit, Pinterest, Google Business, Microsoft Advertising/OneDrive, Slack, …). This always drops the stored credentials, so every tool for that provider stops working immediately and posts/campaigns already published are NOT affected. IT NEVER REVOKES THE GRANT AT THE PROVIDER: the authorisation stays in place, so the same account keeps working in any other brand it is connected to, until the user removes Hermoso in that provider's own settings. The unconfirmed call names where to do that (list_connectors carries revokesAtProvider:"local") — relay it rather than promising a revoke. RECONNECTING NEEDS A BROWSER (the provider's consent screen) — an agent cannot undo this. Name the provider to the user, then call with confirm:true. Use list_connectors for the exact provider ids.

dismiss_apple_ads_recommendation

ChatGPT
Decline an Apple Ads recommendation. NOTHING ABOUT THE CAMPAIGN CHANGES — no budget moves, no CPA moves, no status changes — it records that you reviewed the recommendation and said no, and Apple stops offering that particular one (it may generate a new one later). No confirmation is needed and that is deliberate rather than an oversight: dismissing cannot spend money, and gating the safe direction is what teaches people to pass confirm reflexively on the direction that matters. Only an AVAILABLE recommendation can be dismissed; APPLIED and DISMISSED are terminal, and trying to re-action one is refused for free. Free.

dismiss_microsoft_ads_recommendations

ChatGPT
Dismiss Microsoft Advertising recommendations by id so Microsoft stops offering them. IT CANNOT SPEND and it changes nothing about what the account runs (it takes advice off the list), so it takes no confirmation at all, which is the point: reach for this rather than applying something just to clear it. Nothing is destroyed either: Microsoft still returns a dismissed recommendation from a read, flagged as dismissed, and microsoft_ads_recommendations will show it again with includeDismissed. Ids Microsoft has already withdrawn are reported and are not an error, they are already off the list, which is the outcome you wanted. 0 credits.

download_tiktok_ads_leads

ChatGPT
DOWNLOAD THE ACTUAL LEADS — the names, emails and phone numbers people entered in a TikTok Instant Form. REGION IS REQUIRED AND THERE IS NO "ALL": TikTok keeps US, EEA/CH/UK and everywhere-else leads in three SEPARATE stores and returns only the one you name, so asking for the wrong one returns an empty file that reads exactly like "this ad got no leads". Run it once per region if the ad targets more than one. TikTok restricts lead download to ad-account ADMINS, so a refusal is a Business Center role rather than a broken connection and reconnecting will not fix it. Hermoso creates the download task, polls it and parses the CSV in ONE call because TikTok expires the generated file after 10 minutes. A task that has not finished, or a file TikTok zipped because it exceeded 10MB, is reported as "could not read" — never as "no leads". THE ROWS ARE PERSONAL DATA. Free.

draft_brand

ChatGPT
Fill in a profile (a business brand or a creator) from a website domain, a free-text description, or a social handle, into a {name, products, logo, …} object you can pass to plan_ad / generate. 0 credits. IMPORTANT: a domain can resolve to a DIFFERENT company than intended (e.g. bala.com is an engineering firm, not the Bala fitness brand at shopbala.com). Before spending any credits on research or renders, VERIFY the returned name (and summary) match the brand the user meant; if it looks wrong, re-draft with the correct domain or a description (pass save:false until confirmed) — this tool cannot ask the user, so the caller owns that check.

dub_video

ChatGPT
Localize a finished video into another language WITHOUT re-rendering it: each spoken line is said where it was said, in a native voice, lip-synced, background kept. Pass the video and the language; the script is read off the source (script only overrides it). Paid by length; dryRun quotes. Returns the localized video URL.

duplicate_scheduled

ChatGPT
Copy an existing scheduled or already-published post into a NEW queued post — the way to run a creative again, reuse a post that worked as the starting point for the next one, or re-send something after it went out. It copies the caption, media, per-channel captions, title, description, tags and the target board / Page / company Page / listing, and ANY of those can be overridden in the same call. Give a new time in at, or useQueue:true to drop it into the brand’s next free posting slot. The copy is INDEPENDENT — editing or cancelling it never touches the original — and it is a genuinely new post rather than a re-send, so it publishes even where the original already did. To re-fire only the channels that FAILED, use retry_scheduled instead.

edit_facebook_post

ChatGPT
Use this when the text of a Facebook post the brand already published needs changing. Replaces the post’s message in public. Instagram and Threads posts cannot be edited after publishing. Get postId from list_meta_posts. Free.

edit_image

ChatGPT
EDIT an existing image in place with a plain-language instruction and keep everything else: 'make the headline bigger', 'add our logo bottom right', 'swap the background for a kitchen', 'remove the person on the left', 'erase all the text'. Pass image (URL, Library item, upload_file URL or local path) and instruction. The same edit the web Studio's Edit runs: composition, aspect ratio, people and every untouched line of text stay as they are; the saved brand's real name and website are pinned so an added line never invents one, and the brand's real logo is attached when the instruction asks for the logo. Set removal:true when the edit STRIPS text, branding or an object, so nothing branded is put back. When the saved brand has a product photo and the ad shows that product, the photo rides with the edit and the product's label is read and checked against it: re-printed from the photo only when it came out wrong (the check alone is charged when it is right; one small extra charge finds the product first). fixLabel:false turns that off. For a precise region, pass mask (see generate_image). One image edit's credits; returns the new image URL. For a new image from a prompt use generate_image; to rebuild a competitor's ad for your brand use clone_static.

edit_video

ChatGPT
EDIT an existing clip from a plain instruction (video-to-video): the motion, timing, framing and cut stay, the named thing changes. 'change only the mug to red', 'make it nighttime', 'restyle it as claymation'. Your words are wrapped so the model keeps everything else identical, changes only what you named and repeats that lock; lighting is kept unless the change needs new light (set lighting to force either); literal:true sends your words as written. One change per call holds best. previewFirstFrame:true edits ONE still first (one image edit; previewAt picks the second) and quotes the clip; nothing else runs until you call again, ideally with previewStill. The reply scores how well the shot held outside the change (free) and flags an edit that touched more than asked. NOT for cuts/trims/end cards (post_edit), a new video (generate_video / render_ad), translation (dub_video) or a saved creator's face (recast_motion). Best on 3-15s clips.

edit_x_post

ChatGPT
EDIT the text of one of the connected account’s own posts on X. Three things about X’s edit model change how you must use this and none is guessable: (1) X REPLACES THE WHOLE TEXT — there is no partial patch, so pass the complete new post; (2) an edit MINTS A NEW POST ID, and the old id keeps resolving and keeps showing the OLD text, so always hand the user the NEW url afterwards or they will circulate a link to the version they just corrected; (3) X’s window is ONE HOUR from the ORIGINAL post and DOES NOT RESTART when a post is edited, and each post has a limited number of edits. Pass whichever id the user has — Hermoso reads X’s edit chain and aims at the newest id, which is the only one X accepts (an edit aimed at the id the user was originally given is refused by X once the post has been edited once). A published ARTICLE can never be edited whatever the subscription, and this says so rather than trying. Editing needs X Premium on the POSTING account; Hermoso attempts it and reports X’s own refusal rather than pre-refusing on a guess about the plan. Everything knowable for free — window closed, edits used up, post ineligible — is refused before anything is billed. Costs credits. Needs X connected.

enable_tiktok_brand_hashtags

ChatGPT
Use this when TikTok should count up to 10 hashtags as the brand’s (50 in total). A new hashtag is counted after 24 hours and cannot be turned off for 7 days. A tag must be on the available list or contain the account handle. Free.

error_detail

ChatGPT
One error group in full by fingerprint (from list_errors): every field, plus the most recent redacted occurrences — status, connector, job id, workspace, and a shape-only echo of the inputs. This is what makes a bug reproducible. Read-only, 0 credits.

estimate_messenger_marketing_delivery

ChatGPT
Meta’s own estimate of how many marketing messages a budget would deliver from a Page and what it would cost, before anything is created. Free. Meta marks the metric “in development”, so treat it as a range.

estimate_meta_reach

ChatGPT
Ask Meta how many people a targeting spec can actually reach — BEFORE any budget is committed. Two ways: pass adSetId to size an ad set you already built (Meta uses its own saved targeting), or pass the same targeting object you would give create_meta_ad (plus optional objective / optimizationGoal / country / pixelId) to size an audience you are considering. Returns the monthly-active range, a daily-active estimate, and an explicit warning when the audience is too narrow to deliver. Free, read-only, creates nothing and spends nothing. Use it before recommending a budget and every time the user narrows a geo or piles on interests.

export_swipefile_deck

ChatGPT
Turn a SWIPEFILE COLLECTION into a real Google Slides deck — one slide per saved ad, carrying the creative, the brand, the ad copy, the run dates with the run length, and the platform. This is the thing a marketer actually presents to a client or a team. Returns the presentation id + URL. Creates a NEW deck every time: under the drive.file scope Hermoso can only touch files it created, so it cannot add slides to a deck the user already has. A creative whose ad-library link has expired cannot be embedded — Meta signs those URLs with a short expiry — so that slide says so in words and keeps its copy and run dates, and the reply reports how many. Needs Google Drive connected (Settings > Connectors > Google Drive — one connection covers Drive, Sheets, Docs and Slides).

extend_tiktok_post_ad_authorization

ChatGPT
Extend how long a post the authorized account owns stays promotable as a Spark Ad — and REGENERATE its code if it was deleted. THE DAYS ARE ADDED, NOT SET: TikTok's own example is that a post with 180 days remaining, extended by 180, ends up at 360 — so passing '365' to a post that already has time left does not mean 'expires in a year'. Must be one of 7, 30, 60, 180, 365. Ad authorization has to be ON already (set_tiktok_post_ad_authorization) or TikTok refuses. The reply reads the new window back, because TikTok's own response body is empty. NEEDS THE TIKTOK ACCOUNT AUTHORIZATION (see tiktok_account_status).

fetch_app_screens

ChatGPT
Pull an APP brand's REAL App Store screenshots into the workspace brand, so a screen-hungry native format can use them. Use when the brand has 0–1 app screens on file and you want make_template_ad(template:'app-ui-tour'), or the user asks to 'pull my app's screenshots'. Pass appName (defaults to the saved brand's name). FREE — a keyless App Store lookup. It needs a CONFIDENT match: an ambiguous or unknown app returns 0 screens and saves nothing, which you should relay plainly rather than retrying with guesses. On success the screens are saved to the brand (durable URLs) and are immediately usable.

fetch_asset

ChatGPT
Resolve a generated asset reference (a /generated/… path or any URL) to a clickable absolute URL + a direct download URL.

fetch_bing_webmaster_url

ChatGPT
Ask Bingbot to fetch one URL now, so you can then read back what the crawler actually received with list_bing_webmaster_fetched_urls. THE FETCH IS QUEUED, NOT PERFORMED WHILE YOU WAIT — so "it has not appeared in the fetched list yet" is the normal FIRST answer, and it is reported as "requested, not yet done" rather than as a failure or as success. A URL that is not on this site is refused before anything is requested. 0 credits.

find_competitors

ChatGPT
Discover a brand's competitor / similar / adjacent brands from its domain (Claude grounded by web search). mode=competitors (default, excludes the searched company), inspiration (best relevant ads incl. it), or company. Costs a few credits for the discovery model (no ad-data charge); free inside a new account's first-brand setup.

find_google_ads_locations

ChatGPT
Look up Google Ads location targets by name — turns "Toronto" / "California" / "United Kingdom" into the geo target ids Google needs, with each one's type (COUNTRY, STATE, CITY, POSTAL_CODE…) and reach. Use it when a location name is ambiguous, or to show the user exactly which place you are about to target. Read-only and free.

find_meta_audiences

ChatGPT
Look up the Meta targeting ids you need before building an ad set — interests, behaviours, cities/regions/zips/DMAs, languages, employers, job titles and schools. type:"adinterest" (q:"yoga") returns interest ids + audience size; type:"adTargetingCategory" with class:"behaviors" returns behaviour ids; type:"adgeolocation" (q:"Toronto", optionally locationTypes:"city") returns the geo KEYS that go in targeting.geo.cities/regions/zips; type:"adlocale" (q:"french") returns language ids for targeting.locales. Read-only and free. Use it whenever the user names an audience in words — never guess an id.

find_sound

ChatGPT
A sound for an edit by name ('the FAAAA sound'), by the moment ('bad news reaction') or by link (TikTok sound, meme-sound page, post, audio file): a durable mp3 and where it starts and lands. Named/described sounds come from what TikTok uses now; pick takes another candidate.

finish_video

ChatGPT
Post-process an EXISTING rendered video (its served mp4 URL) with the proven direct-response 'reviewer' finish and/or a film-grain pass — no AI model, ~30s, a couple of credits. pills=true composites a header pill (e.g. '10/10 would buy again'), a brand-accent sub-pill, and 3-4 green-check proof pills cascading in on the beat (YOU author the copy: header ≤40 chars, sub ≤34, each point ≤44 — concrete real benefits, never fabricated stats). grain=true applies a subtle camera-grain finish that makes photoreal AI renders look phone-shot ('less AI') — works alone or with pills. Returns a NEW video; the original is untouched.

fix_beat

ChatGPT
Surgically re-render ONE time window (1.5-8s) of an existing rendered video and splice it back on the VIDEO TRACK ONLY — the rest of the video and ALL audio stay byte-identical. Use when one beat/shot is broken ('the shot at 8 seconds glitches') and a full re-render would waste the parts that worked; bills only the replacement clip's seconds (~1/3 of a full render). Do NOT pick a window covering spoken dialogue (a video-only splice under speech breaks lip-sync) — pass speechWindows to enforce this.

forget

ChatGPT
Delete a saved Memory item by its id (from list_memory). Records a cross-device delete so it doesn’t come back. Minor + re-creatable (you can remember it again), so no confirm needed.

format_sheet

ChatGPT
Make an exported sheet readable: bold the header row, FREEZE it so it stays visible while scrolling, and auto-size the columns so nothing is cut off. Worth calling right after create_sheet — a raw export with unsized columns and a header that scrolls away is the difference between a spreadsheet someone reads and one they close. Changes no cell VALUE, so it is never gated. The defaults do all three on the first tab; pass tab to pick another, freezeRows:0 to skip freezing, boldHeader:false or autoResize:false to skip those.

generate_avatar

ChatGPT
ANIMATE A PHOTO so it talks: the presenter in image says script word for word, a separate voice lip-synced onto the still. Use it ONLY when the user asks for an animated photo / talking photo / lip-sync look by name; for any other talk-to-camera or spokesperson ask use generate_video with speak (the person filmed saying it, which reads as real footage). About 1-3 min, holds the pose steady, 480p/720p. The per-second credit price is in hermoso_capabilities (avatarEngines); dryRun:true returns this exact job's hold without rendering. Blocks until done where the host allows, else returns a job id to poll with get_job. Requires canAvatar. Spends credits; a refusal before rendering costs nothing.

generate_image

ChatGPT
Use this when the user asks for an image ad, ad creative, product photo or social post graphic for their business or product. It renders a finished image on a real image model, on their saved brand (product photos, logo), and returns a URL ready to post. For a video, use render_ad. refImages (paths or URLs) composite a real product into the scene. MULTI-BRAND: hydration uses the SAVED brand; for a different brand (a fresh draft_brand) pass its own productImages/logo as refImages with useBrand:false, or the wrong product lands in the shot. Product photos also pick the ENGINE (the compositing model), so a named model is honoured only when no references ride; raw:true or useBrand:false keeps it. model = a catalog id from hermoso_capabilities (omit for the default). A PRODUCT IN A PERSON’S HANDS, or a garment on them: name an id marked needsRefs with a refsMax; person photo first, then up to three product/garment photos. It EDITS THE PHOTOGRAPH: the person is RE-POSED (stance and hands change; face, clothing, setting and light are kept), so it is the wrong tool when the rest must stay untouched. RAW MODEL ACCESS: raw:true dispatches your prompt to the model BYTE-IDENTICAL — no rewriting, no appended guidance, no negative prompt, no brand references attached on your behalf. Credits, the durable delivery of the finished asset and the per-model validation are unchanged. Seconds. Spends credits.

generate_music

ChatGPT
RAW music: describe it (genre, mood, instruments, tempo), get an instrumental MP3. Flat fee: explainerMusicCredits in hermoso_capabilities.

generate_text

ChatGPT
Text generation against the writing-model catalog (Claude, Gemini, GPT, Llama, DeepSeek…) — ad copy, hooks, scripts, rewrites, brainstorms. Prompt-only, no ad assembly (for a finished on-brand creative use plan_ad -> render_ad). BY DEFAULT the model answers as a marketing copywriter (a short house system prompt is applied, which is what you want for ad copy); pass raw:true for a plain, unstyled answer from the model itself with NO system prompt at all. model = a writing-model id from hermoso_capabilities (omit for the default Claude orchestrator). Paid (a credit or two by length).

generate_video

ChatGPT
Use this when the user asks for a video clip, b-roll or a shot from a description, or names a video model. For a finished video ad, Reel, TikTok ad or UGC video for their business, use render_ad. It renders one continuous clip on a real video model and returns its mp4 URL. A clip runs up to the model’s longest listed duration (the longest takes a multi-beat 30s spot in ONE take with native audio), so never assume an 8–10s cap or stitch what fits one clip; durationSeconds must be one of the model’s durations in hermoso_capabilities. NAME A MODEL in model to get it: an unnamed render uses a narrower auto-pool. 1–3 min. refImage anchors the first frame; ttsScript adds a voiceover. AUDIO IS NOT FREE BY DEFAULT: a clip with no audio of its own gets a music bed composed and CHARGED on top (see musicMood and audio), which can cost as much as a cheap draft clip. refVideo (a clip URL) EDITS that clip instead, keeping its canvas and length. RAW MODEL ACCESS: by default small guards are added (label safety with no reference image, a negative prompt where the model takes one, reference lines) and hex colours become names; raw:true dispatches your prompt to the model BYTE-IDENTICAL — no rewriting, no appended guidance, no negative prompt, no brand references attached on your behalf. Credits, the durable delivery of the finished asset and the per-model validation are unchanged. Spends credits (blocked on the Starter plan).

generate_voice

ChatGPT
RAW text-to-speech from the voice-model catalog: speak a script in a chosen voice and return the served MP3 URL. For a standalone voiceover / narration clip — NOT for adding audio to a video (render_ad and generate_video voice their own spots; change_voice re-voices a finished clip). engine picks the voice model (default 'seed-audio'; also 'eleven-v4' (Eleven v4; the retired id 'eleven-v3' still answers, on v4), 'minimax-speech', 'kokoro'); voice is a preset name from that engine (see hermoso_capabilities -> voice engines) — a name that engine does not have is REFUSED for free with its real list, and a few engines generate their own voice and take no preset at all (the reply says which voice actually spoke). Paid (a couple of credits by length; ≤900 characters).

get_analytics_stream_setup

ChatGPT
THE GTAG SNIPPET TO PASTE, plus what that web stream is actually set to collect. list_analytics_data_streams gives the measurement ID and stops one step short of what someone installing GA4 needs, which is the finished <script> block — Google returns it verbatim and marks it immutable. The same call answers the two questions that come next: whether ENHANCED MEASUREMENT is collecting scrolls, outbound clicks, site search, video, downloads, form interactions and single-page-app page views, and whether client-side REDACTION is stripping emails or query parameters out of recorded URLs. Read the master switch before believing a toggle: if enhanced measurement is off for the stream, every individual toggle is inert whatever it says. WEB STREAMS ONLY — all three resources are web-stream resources, and an app stream is refused by name with what it has instead. Any half that could not be read comes back null, which means unknown and not off. Read-only, free.

get_apple_ads_ad_account

ChatGPT
The full record for an Apple Ads ad account — its name, currency, timezone, payment model, productFeatures and the advertiser resources delegated to it. Called with no id it returns the account this connection is pinned to. THE FIELD THAT DECIDES WHAT THIS ACCOUNT CAN DO IS productFeatures, and it is IMMUTABLE: APPSTORE_APP_MANUAL can never run Apple Maps ads and BUSINESS_BRAND_MANUAL can never run App Store ads, so an organization that needs both keeps a separate ad account for each. currency, timeZone and paymentModel are inherited from the organization and are immutable too — which is why a campaign’s amounts must be in this currency and cannot be mixed. Read-only, free.

get_brand

ChatGPT
What Hermoso ALREADY KNOWS for the active profile: the same saved profile (products, logos, palette, positioning) + learned memory the web Studio uses. Call it when you need to know whether a brand is on file: if hasBrand is true you can omit brand everywhere; if false, onboard with draft_brand. Not a required first step before a render: the create tools read the saved brand by themselves. 0 credits.

get_business_location

ChatGPT
Read everything Google holds on one of the brand’s Google Business Profile listings — business name, address, phone numbers, website, categories, description, regular and special hours, service area, labels, store code, open state, and whether the listing can carry a Post at all. This is the listing AS THE MERCHANT LAST SET IT, which is exactly what update_business_location edits; it can differ from what Google Maps shows today, because Google and the public can suggest changes on top. Call it before offering to change anything, and to answer “what does our Google listing actually say?”. Read-only, 0 credits. Needs Google Business Profile connected (Settings > Connectors > Google Business Profile).

get_drive_file

ChatGPT
Fetch one Drive file’s metadata — name, type, size, modified time, a webViewLink to open it and a webContentLink to download it. Pass fileId (from list_drive_files). Read-only.

get_job

ChatGPT
Poll a render job by id. Returns status (queued|running|done|error), progress, and on done the served media URL. Renders take 1–3 minutes: keep calling this until done/error without asking the user — several calls is normal, not a stall. An id that does not exist on this account answers status "not_found" — that is FINAL: stop polling it, and do not re-fire the render (that double-charges).

get_linkedin_lead

ChatGPT
One LinkedIn lead by id (from list_linkedin_leads), with every answer named by field. Personal data — show, never republish. Read-only, free.

get_messenger_marketing_message_status

ChatGPT
Whether a paid Messenger marketing message was delivered. send_messenger_marketing_message returns a tracking id, which only means Meta ACCEPTED it; this reads what Meta pushed back on the Page webhook for each tracking id: sent, delivered (the event Meta bills on), read, clicked (with count), or failed (with Meta’s reason). "unknown" means NO event has arrived, never that it failed: events arrive only when the Page is subscribed to the marketing_message_* fields (the reply says whether it is, read back from Meta; fix with subscribe_meta_webhooks fields ["marketing_messages"]) and only for messages sent after Hermoso began recording them on 2026-09-22. A failure carries no tracking id, so it is matched through Hermoso’s own send log. Read-only, 0 credits.

get_onedrive_file

ChatGPT
Fetch one OneDrive item’s metadata — name, type, size, modified time, a webViewLink to open it and a webContentLink to download it. Pass fileId (from list_onedrive_files). Read-only.

get_openai_ads_audience

ChatGPT
Read one ChatGPT Ads custom audience: its processing status, how many users matched, what it can actually be USED for, and its membership revision. Read-only, free. STATUS is not the same question as eligibility — "ready" only means processing succeeded. EXCLUSION (suppression) has NO minimum size, but INCLUSION and bid multipliers need roughly 25,000 matched users, so a small list used for inclusion simply never serves. The membershipRevision this returns is what update_openai_ads_audience_members REQUIRES for a replace.

get_openai_ads_audience_operation

ChatGPT
Poll an add/remove/replace submitted by update_openai_ads_audience_members until it reports succeeded or failed. Read-only, free. Until it succeeds the membership has NOT changed, so never report an audience update as done on the strength of the submission alone.

get_openai_ads_business_agent

ChatGPT
One Business Agent with its full configuration and publish state. Read-only, 0 credits.

get_openai_ads_feed_settings

ChatGPT
A product feed’s name, countries and whether its organic ChatGPT search listing respects each product’s is_eligible_search flag. Read-only, 0 credits.

get_openai_ads_feed_sftp

ChatGPT
Whether SFTP bulk delivery is enabled for a ChatGPT Ads product feed, its connection URI and authentication method. Read-only, 0 credits.

get_openai_ads_landing_page_crawl

ChatGPT
The saved evidence from OpenAI’s landing-page crawler for an ad’s submitted version: crawl status and failure code, the requested and final URL (a redirect shows here), capture time, whether robots.txt allowed the crawler and its user agent, and a screenshot of what it saw. Read it when an ad is rejected or reports a landing-page crawl serving issue. The screenshot link expires within 24 hours. available:false with pending:true means the capture is still running; available:false otherwise means OpenAI holds no capture (or the ad id is not on this account). The status is the crawl job’s, not the page’s HTTP status. Reading never starts a crawl. Read-only, 0 credits.

get_openai_ads_lead_form

ChatGPT
One ChatGPT Ads lead form with its fields and revision ids (pass revisionId to read a specific revision). Read-only, 0 credits.

get_post_refill

ChatGPT
Show AUTOPILOT POSTING (autoposting, the posting refill) for this profile — "is autopilot on?", "what is waiting for review?": whether it is on, its mode (review = each batch of fresh posts waits as drafts for approval; auto = fresh posts are scheduled straight away), WHICH CHANNELS it posts to with each one’s posts per day and posting times, roughly what it costs a day, when it next runs, how many posts are queued, the DRAFTS waiting for approval (id, time, channels, caption, the new image or video), and the notes it learned from reviews. It also names the channels that CANNOT be posted to and why. Read-only, free.

get_reddit_ads_asset

ChatGPT
One stored Reddit creative asset by id — type, name, status, media dimensions and urls, or the headline/CTA text, plus any processing errors Reddit holds for it. Free.

get_reddit_ads_asset_uploads

ChatGPT
The processing state of one or more Reddit creative-asset uploads (ids from upload_reddit_ads_assets): PROCESSING, ACTIVE with the stored asset, or INVALID_MEDIA with Reddit’s reasons. Ids Reddit no longer knows are listed as missing, never silently dropped. Free.

get_reddit_ads_max_template

ChatGPT
Read a Reddit Max template ad: its status, its assets, its destination and whether Reddit has finished generating ads from it (materialization SUCCESS, PARTIAL or FAILED with Reddit’s reasons, or still generating). Poll this after create_reddit_ads_max_campaign or update_reddit_ads_max_template. Read-only, free.

get_settings

ChatGPT
Read this account's app settings — the LANGUAGE Hermoso writes ads, copy and answers in, the app appearance (theme), and whether the weekly competitor-watch email is on. Same settings as the web app's Settings pane. Read-only, free.

get_skill

ChatGPT
Load a bundled skill’s full SKILL.md workflow instructions by name (from list_skills). Follow the loaded instructions to run that workflow with the other tools. Read-only, free.

get_tiktok_ads_brand_safety

ChatGPT
What content this TikTok ad account’s ads are allowed to appear next to — the inventory filter tier (EXPANDED / STANDARD / LIMITED / NO_BRAND_SAFETY) plus the suitability controls: which content categories are excluded and which vertical’s sensitive content is avoided. It also returns, free and in the same call, THE CATALOGUE of every category id you could set, because those ids are unguessable and there is otherwise no way to act on the setting. Warning: THE SCOPE IS THE THING TO KNOW: TikTok applies this to future Smart+ campaigns and, in their own words, "will not apply to future regular campaigns created using /campaign/create/" — which is the endpoint create_tiktok_ads_campaign uses. So this is the ad account default and the Ads Manager setting, NOT a guarantee inherited by a campaign Hermoso builds. A NULL vertical-sensitivity catalogue means this advertiser is not on TikTok’s allowlist for that feature, never that no vertical categories exist. Read-only, free.

get_tiktok_mention

ChatGPT
Read the full detail of a SINGLE mention: a mentioning post, or a mentioning comment if you pass commentId as well. This exists for the TikTok mentions WEBHOOK: the event carries only ids, and this turns one into the caption, the creator, the engagement and the thumbnail. TIKTOK ONLY GUARANTEES THE DATA FOR 48 HOURS after the event fires, so a queue that retries tomorrow gets nothing. For anything older read it out of list_tiktok_mentions or list_tiktok_mention_comments instead. itemId is required either way, comment or not. NEEDS THE TIKTOK ACCOUNT AUTHORIZATION with the brand-insights permission. Read-only, free.

get_tiktok_post_ad_authorization

ChatGPT
Read the Spark-Ads authorization status of a post the authorized account owns — whether it is promotable, its authorization CODE, and the window the authorization runs for. A SPARK AD CANNOT OUTLIVE ITS AUTHORIZATION, so check the end time before building a campaign around a post. TIKTOK ERRORS RATHER THAN ANSWERING when a post has no authorization code at all, so a not-found style refusal from here almost always means Ad authorization was never turned on (or the code was deleted) — turn it on with set_tiktok_post_ad_authorization — and not that anything is broken. NEEDS THE TIKTOK ACCOUNT AUTHORIZATION (see tiktok_account_status). Read-only, free.

get_tiktok_tto_spark_authorization

ChatGPT
Whether a creator has approved Spark Ads authorization for their video, and if so the authorization CODE, the window it covers, and whether that code is already bound to an ad account. The code is what authorize_tiktok_ads_spark_post takes. It appears only once the creator accepts: nothing on this side can produce one; and TikTok also reports how many further requests are left. Read-only, free.

google_ads_change_history

ChatGPT
WHAT CHANGED ON THE ACCOUNT, AND WHEN — the answer to "performance fell off a cliff on Tuesday, what happened?", and the Google twin of reddit_ads_history. source:"change_event" (default) is FIELD-LEVEL over the last 30 days: the change time, who made it, from which client (web UI, API, scripts, bulk upload, automated rule), whether it was a CREATE / UPDATE / REMOVE, and exactly which fields moved — with detail:true it also carries the old and new resource snapshots. source:"change_status" reaches 90 days and is the ONLY one that catches GOOGLE ADS EDITOR and criterion-level edits: Google documents change_event as NEVER returning Editor changes, so an Editor-managed account looks completely untouched there. CHECK BOTH BEFORE TELLING ANYONE NOTHING CHANGED. An unknown source is refused by name; the 30/90-day windows and Google’s own 10,000-row cap are enforced here with the reason instead of surfacing as an unreadable Google error, and a change takes up to three minutes to appear. Neither resource carries any metric or segment, so this says what changed, never what it cost. Read-only, 0 credits.

google_ads_keyword_ideas

ChatGPT
Google’s own KEYWORD PLANNER: real keyword ideas with average monthly search volume, competition level and top-of-page bid estimates, so keyword choices are measured instead of guessed. Seed it with keywords[] (terms you already have), url (one landing page to mine) or site (a whole domain — the fastest way to size a competitor). Narrow by locations (place NAMES, resolved for you) and language. Results come back sorted by monthly volume. Use this BEFORE add_google_ads_keywords or create_google_ads_campaign so the ad group targets terms people actually search, and quote the volumes when you propose them. Read-only, free, spends nothing and creates nothing.

google_ads_report

ChatGPT
Run a GAQL (Google Ads Query Language) report for detailed performance breakdowns — ad groups, ads, keywords, search terms, demographics, geo. Pass customerId + a GAQL query (SELECT … FROM <resource> WHERE segments.date DURING LAST_30_DAYS). Allowed FROM resources: campaign, ad_group, ad_group_ad, keyword_view, campaign_budget, campaign_criterion, ad_group_criterion, conversion_action, campaign_conversion_goal, search_term_view, age_range_view, gender_view, geographic_view, asset, change_event and every other documented GAQL report resource (the refusal names the full list if one is missing). Campaign network settings (Search partners / Display) are readable here as campaign.network_settings.* and changed with set_google_ads_networks. cost_micros is micros — divide by 1,000,000 for the account currency. Read-only, free.

google_business_account

ChatGPT
Read the Google Business Profile ACCOUNT that owns one of the brand’s listings — the account name, its type (a personal Google account, a location group, a user group or an organization), the connected user’s role on it (primary owner / owner / manager / site manager), the account’s verification state and the permission level. Use it to answer “can we actually edit this listing?” and “whose account is it on?” before offering an edit that Google would refuse anyway. It reads exactly ONE account — the parent of a listing already shared with this profile — and never lists the other accounts the connected Google login can reach; that roster belongs to the account picker (list_connector_accounts). Read-only, 0 credits. Needs Google Business Profile connected.

google_business_insights

ChatGPT
How the brand’s Google Business Profile listing actually performed — impressions on Google Search and Maps (desktop and mobile), calls, website clicks, direction requests, messages and bookings — over the last N days. For a local business this is the real-world demand signal, and it is the number an ad campaign should be judged against. NOTE: Google discontinued PER-POST insights in February 2023 and published no replacement, so these are listing-level figures and per-post performance genuinely does not exist in any API — do not promise it. Read-only, 0 credits. Needs Google Business Profile connected.

google_business_search_keywords

ChatGPT
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.

headline_variants

ChatGPT
Turn ONE finished static ad into several copies that differ ONLY in the headline, for an A/B test: same picture, product, layout, colours and every other line. Pass image, and either headlines (your own, up to 10) or count (default 5, max 10) to have distinct angles written for you in the saved brand's voice (never inventing numbers, prices, ratings or claims the ad or brand does not state); brief steers what to test. The ad's text is read first (3 credits), then one image edit per headline; each output is proofread and flagged (textCheck) if the rendered words do not match, never silently re-rendered. When the saved brand has a product photo and the ad shows that product, the photo rides with the edit and the product's label is read and checked against it: re-printed from the photo only when it came out wrong (the check alone is charged when it is right; one small extra charge finds the product first). fixLabel:false turns that off. The whole batch is priced before anything runs. Returns each headline, its angle and its image URL.

hermoso_capabilities

ChatGPT
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.

hermoso_credits

ChatGPT
Return the account credit balance, the credits this account has spent on the calls listed, those recent priced calls, 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 are FREE on every plan (X is the single per-call exception). Check before kicking off paid generation; answer "does posting cost credits?" with NO.

hide_meta_comment

ChatGPT
Use this when a comment on the brand’s own Facebook or Instagram post should be hidden from the public. Reversible with unhide_meta_comment. Get commentId from list_meta_comments. Free.

hide_thread_reply

ChatGPT
Hide a reply on the brand’s Threads post (or unhide it with hide:false) — for spam and abuse moderation.

hide_tiktok_comment

ChatGPT
Use this when a comment on the brand’s own TikTok post should be hidden, including a stranger’s (only the brand’s own comments can be deleted). Reversible. Free.

hold_youtube_comment

ChatGPT
Use this when a comment should be hidden while it waits for review. Reversible with approve_youtube_comment. Free.

hook_variants

ChatGPT
HOOK MULTIPLIER: give ONE finished video ad N NEW OPENING HOOKS, N complete versions to A/B test. A planned hook replaces the first ~1.5-4 s (ends at the source's first cut there, else 3 s; hookSeconds overrides) with a new silent shot on a DIFFERENT named mechanic (list_hooks); the rest of the footage and the WHOLE original soundtrack stay, so every version keeps the source's length, and nobody in the hook talks or shows text. hooks[] adds your own openings: {url} a FOUND viral hook (post link or file) joined in front with the approved bridge (cut before its payoff, its own payoff sound carried across; post_edit price), {prompt} an opening described in words, {mechanic} a named one; with hooks and no count only those are made. To make the found hook's subject your product or creator first: recast_hook. Pass the video's FILE URL (a render, job result, list_library or upload_file). 1-5 versions, default 3. Refused free before billing: a source over 120 s, too short for a 1.5 s hook plus 2 s after it, unreadable, or a social post as the SOURCE (remake_video_for_brand remakes someone else's ad). COST: a small planning read, then each planned version is billed like fix_beat for the hook's seconds; the reply quotes credits, and dryRun:true returns the plan and quote without rendering (pass that plan back to render exactly those). Returns ONE JOB PER VERSION; call get_job on each until done, never describe a version before its URL arrives. Hooks that show the product use the brand's product photo (productImage overrides; useBrand:false sends none).

hubspot_deals_report

ChatGPT
Deals from the brand's HubSpot created in the window (default the last 90 days): count and amount by stage (labels resolved through the deal pipelines, closed-won recognised from the stage's own metadata), by original source, a monthly created / closed-won series, and totals. Capped at 2,000 deals and says so. Read-only, free. Needs HubSpot connected.

import_from_cloud

ChatGPT
Pull the files in a Google Drive or OneDrive FOLDER into this profile's Library, so they can be used like anything rendered here — published, scheduled, cloned, used as a product photo or a reference. Hermoso downloads each file with the user's own connected account (a Drive/OneDrive file is not public, so this is the only way in) and stores a durable Hermoso url for each. Give folderId from list_drive_files / list_onedrive_files with onlyFolders — omit it for the root. GOOGLE DRIVE ONLY SHOWS WHAT THE USER HANDED OVER: our Drive scope is drive.file, so Hermoso can see the files and folders it created plus the ones the user picked with the Google picker in the app, and NEVER their whole Drive — if a folder comes back empty, that is the answer, and the user picks it in the app once to make it reachable. OneDrive has no such limit. SUBFOLDERS ARE NOT WALKED and Google-native docs (Docs/Sheets/Slides) have no file to download: both are reported back BY NAME rather than silently dropped, along with anything too large or unreadable, so you can tell the user exactly what did and did not come across.

instagram_collaborators

ChatGPT
Did the collab invites on an Instagram post get accepted? Reports every collaborator on one of the brand’s OWN Instagram posts with the status Instagram actually holds for it — Accepted (the post is live on their profile too, and its reach now includes their followers), Pending (invited, sitting in their notifications, NOT yet on their profile) or Declined. This is the tool for “did @creator accept yet?”, and the only way to find out: Instagram sends no notification either way. The media id is what post_to_meta returned as postId, or any id from list_instagram_media. A post with no collaborators simply reports none. Read-only, 0 credits.

instagram_insights

ChatGPT
ACCOUNT-level performance for the brand’s connected Instagram Business account — views, reach, accounts engaged, total interactions, likes, comments, shares, saves, profile link taps, replies, reposts and follows/unfollows — plus the AUDIENCE DEMOGRAPHICS (follower_demographics and engaged_audience_demographics, broken down by age, city, country or gender), which is the read that says WHO the content reached rather than how many. Use meta_post_insights for one post and meta_page_insights for the Facebook Page. THERE IS NO "impressions": Meta deprecated it for every API version on 2025-04-21 and replaced it with "views" — an unknown metric is refused by name rather than quietly dropped. Instagram returns NO demographics for an account under 100 followers (or under 100 engagements in the window), and an absent block means exactly that, never an empty audience. Read-only, 0 credits. Needs the Instagram account connected, either through Meta (an Instagram account linked to a Facebook Page) or on its own through the Instagram connector; when several are connected, name the one you mean with account.

instagram_profile

ChatGPT
Turn an Instagram @handle into the NUMERIC Instagram account id, straight from Meta — with the account’s real name, bio, follower count, post count, website and profile picture. Two jobs: it answers "what is @<handle>’s Instagram id?", and it sizes up any brand or creator on Instagram before you work with them (followers and post count are Meta’s own numbers, not a scraper’s). CALL IT BEFORE manage_meta_partnership_creator: that tool’s allowTagging list takes NUMERIC IDS ONLY, and an id that did not come from Meta is rejected there with “An unknown error has occurred.” — an error carrying no cause at all, so a scraped or guessed id looks like an outage instead of a bad argument. PROFESSIONAL ACCOUNTS ONLY: Meta reads Instagram Business and Creator accounts here and nothing else. A personal account, a private account, an age-gated account and a handle that does not exist all come back as the SAME refusal, because Meta returns one error code for all four — so never tell a user which of them it was. Read-only, 0 credits, no new permission, no reconnect.

intercom_customer_language

ChatGPT
What CUSTOMERS wrote in the brand's Intercom: reads recent conversations in full and returns only the customers' own words (no teammate or bot text; emails, phone numbers and links redacted; no names). Use it for real pains, objections, questions and exact phrases for ad copy, hooks, angles and research, and quote their words rather than paraphrasing. days (default 30), conversations to read (default 25, at most 60), optional text or tagId to focus it. Read-only, free. Needs Intercom connected (Settings > Connectors > Intercom).

invite_member

ChatGPT
Invite someone to this profile by email (role: member = read-only on billing, admin = full). This SENDS a real email invite / share link — an account change. Confirm the exact email + role with the user, then call with confirm:true.

leave_bluesky_convo

ChatGPT
Use this when the account should leave a Bluesky DM conversation. It cannot be undone from here; say so first. Free.

leave_connector

ChatGPT
On a connector that several teammates can contribute their OWN account to (see list_connectors — the row reports multiContributor), remove YOURS from this profile: your stored credential is dropped and the accounts you shared stop being shared. Your teammates' accounts on the same connection keep working, and nothing changes at the provider — reconnecting in a browser shares again. Use this instead of disconnect_connector when the connection is not yours to remove: disconnect_connector revokes the grant at the provider and only the person who created the connection may call it.

leave_x_chat

ChatGPT
Use this when the brand should leave a group conversation. Needs confirm:true. Needs X connected; X bills per call, so it costs credits.

like_instagram

ChatGPT
Like (or unlike) an Instagram post, Reel, comment or reply AS the brand’s Instagram account — Meta’s Like Media and Comments API (April 2026). The cheapest engagement a brand does: like the good comments on your own posts and the posts you are tagged in. Pass exactly one of mediaId or commentId; undo:true unlikes. Stories and private accounts cannot be liked. Needs the Meta (Facebook Login) connector — a direct Instagram login has no likes edge. Roughly 200 likes per account per hour. Free.

like_tiktok_comment

ChatGPT
Use this when the brand should like a comment on its own TikTok post. Pass videoId too and the reply reads the comment back. Get commentId from list_tiktok_comments. Free.

linkedin_ads_report

ChatGPT
LinkedIn ad performance — impressions, clicks, cost, website conversions, leads and social actions — pivoted by CAMPAIGN (default), CAMPAIGN_GROUP, CREATIVE, ACCOUNT, CONVERSION, PLACEMENT_NAME, IMPRESSION_DEVICE_TYPE, SERVING_LOCATION… or by AUDIENCE DEMOGRAPHICS: MEMBER_COMPANY_SIZE, MEMBER_INDUSTRY, MEMBER_SENIORITY, MEMBER_JOB_TITLE, MEMBER_JOB_FUNCTION, MEMBER_COUNTRY_V2, MEMBER_REGION_V2, MEMBER_COMPANY. The MEMBER_* pivots report job title, seniority, company size and the other member demographics. LinkedIn allows exactly ONE pivot per report, so ask for them one at a time and join the answers yourself. An unknown pivot or granularity is refused BY NAME rather than forwarded. On a demographic pivot LinkedIn returns only the top 100 values, DROPS any value under 3 events (so the rows will not sum to the campaign total) and lags 12–24 hours behind the performance numbers — the note says so, every time. Window via since/until (YYYY-MM-DD). ZERO rows genuinely means no delivery in that window; say exactly that and never present zeros as measured performance. A LinkedIn TEST ad account NEVER returns analytics, and the note says so when that is what you are looking at. Read-only, free.

linkedin_audience_count

ChatGPT
HOW MANY LINKEDIN MEMBERS a targeting spec reaches, before any budget is committed — the cheapest sanity check there is on a B2B audience, and it needs no ad account. Pass locations plus optional include:{titles, industries, seniorities, staffCountRanges, jobFunctions, skills, …}; search_linkedin_ads_targeting resolves any of those names to the URNs LinkedIn demands, free. THE CRITICAL THING TO SAY WHEN REPORTING: a returned total of 0 means the audience is UNDER 300 PEOPLE, not that it is empty — LinkedIn suppresses any count below 300 to protect member privacy, and 300 is also the minimum audience a campaign may run against, so a 0 means this targeting is too narrow to advertise to. The figure is a rounded approximation, so quote it as an estimate and never as a headcount. Read-only, 0 credits.

linkedin_bid_pricing

ChatGPT
LinkedIn’s OWN suggested bid and daily-budget range for a specific audience — the suggested bid with a low/mid/high range, the hard bid limits, and the minimum, default and maximum daily budget, all in the ad account’s currency. Use it before proposing a number to a user instead of guessing what LinkedIn costs, and pair it with linkedin_audience_count to answer "can we afford this audience?" in one go. Below LinkedIn’s minimum bid it says delivery "may be poor" for Sponsored Update campaigns and is impossible for every other format. These are ESTIMATES for this audience, not prices, and nothing is committed until a campaign is activated with set_linkedin_ads_status(confirm:true). Read-only, 0 credits.

linkedin_page_analytics

ChatGPT
ORGANIC performance for one of the brand’s LinkedIn COMPANY PAGES: total followers, followers gained (organic vs paid) across the window, Page views (all / unique / desktop / mobile), and the impressions, unique impressions, clicks, likes, comments, shares and engagement rate of the Page’s posts. This is what answers “is our LinkedIn actually working” and “did that post land”. It is NOT linkedin_ads_report — that covers PAID campaigns; LinkedIn excludes sponsored activity from these figures entirely. Pass postUrns (the urn:li:share:… / urn:li:ugcPost:… that post_to_linkedin_page returned) for PER-POST numbers; LinkedIn forbids a date range together with named posts, so that switches to lifetime-per-post. Only Pages the user ticked in Manage accounts (or call list_connector_accounts with provider linkedin, then set_connector_accounts) are readable — a Page the account merely administers is refused, by design. LinkedIn keeps 12 months, follower figures run about 2 days behind, and it OMITS posts with no recorded activity rather than returning zeros: report an absent post or an unavailable section as MISSING data, never as zero. Read-only, 0 credits. Needs LinkedIn connected with the organization scopes.

list_amplitude_annotations

ChatGPT
The markers already on this Amplitude project's charts — releases, campaigns, incidents — with their timestamps, labels and categories. READ THESE BEFORE EXPLAINING A CHANGE IN A CHART: a spike that lines up with an annotation has an explanation somebody already wrote down, and reporting it as a mystery when the answer is sitting on the timeline is exactly the failure this closes. Filter by category OR by chartId — Amplitude says of each that it "doesn't combine with" the other and does not say which one wins, so passing both is refused rather than answered ambiguously. start and end are ISO 8601 (e.g. '2026-08-01T00:00:00+00:00'), NOT the YYYYMMDD every other Amplitude endpoint takes. Read-only, 0 credits.

list_amplitude_events

ChatGPT
The Amplitude TAXONOMY — the project's declared events, event properties, user properties or group properties, so a query names something real instead of a guess. category is event (default) / category / event-property / user-property / group-property. IF AMPLITUDE REFUSES THIS, REPORT THEIR REFUSAL AND MOVE ON. The Taxonomy API is widely believed to require an enterprise/Govern entitlement, but Amplitude documents no such gating on either the taxonomy page or the Dashboard API page (both checked), so a 403 here is surfaced as AMPLITUDE'S OWN message and is never presented as a Hermoso plan rule, as a broken connection, or as a fact about the user's plan that we cannot actually know. Every other Amplitude tool is unaffected by that refusal. Read-only, 0 credits.

list_analytics_audiences

ChatGPT
The GA4 audiences (remarketing lists) on a property, WITH whether they can actually be used in Google Ads — it reads the Google Ads link state alongside them, because an audience on an unlinked property is a silent dead end that reports no error anywhere. ARCHIVED audiences are not listed, so absence here is not proof one never existed. GA4's PREDEFINED audiences ('All Users', 'Purchasers') ARE returned — verified live against a real property — and they come back with clauseCount 0, because Google defines them internally rather than with filter clauses; that is normal and is not a broken audience. Read-only, 0 credits.

list_analytics_data_streams

ChatGPT
The GA4 data streams on a property, with the measurement ID (G-...) each one carries and how many Measurement Protocol secrets it already has. THIS IS WHERE A GTAG OR GTM INSTALL GETS ITS ID, and it is the first call before sending any server-side event. A web stream has a measurementId; an app stream has none and uses its firebaseAppId instead, so do not report an app stream as broken for having no G- id. A stream whose secret count could not be read comes back as null, which means unknown and not zero. Read-only, free.

list_analytics_definitions

ChatGPT
What a GA4 property already MEASURES — its key events (what GA4 counts as a conversion) and its custom dimensions, with each dimension's parameter name and scope. Two reasons to call it: to learn a property's own custom dimension names before using them in analytics_report, and to CHECK BEFORE CREATING — a custom dimension can never be deleted, only archived, and a property is capped at 50 event-scoped ones, so creating a duplicate permanently burns a slot. The property is the NUMERIC id from list_analytics_properties. Read-only, 0 credits.

list_analytics_metadata

ChatGPT
THE VOCABULARY OF ONE PROPERTY — every dimension and metric analytics_report will accept on it, INCLUDING that property's own custom dimensions, each with its api name and its human label. Use this instead of guessing an api name: GA4 publishes hundreds and they are not memorable (sessions broken down by landing page is landingPage, revenue is totalRevenue, the channel grouping is sessionDefaultChannelGroup), and a wrong name is an error mid-conversation rather than a suggestion. analytics_report deliberately forwards names AS GIVEN — it never validates against a copied list, because that list would go stale and start refusing names Google accepts — so THIS is where a name is checked. PASS search almost always: unfiltered this returns several hundred rows, and a search matches both the api name and the label ("revenue", "campaign", "device"). Read-only, 0 credits.

list_analytics_properties

ChatGPT
The GA4 properties SHARED WITH THIS PROFILE, with their numeric property ids, display names and the Analytics account each sits under. CALL THIS FIRST — every other Analytics tool needs a PROPERTY ID, which is NUMERIC (e.g. 123456789) and is NOT the "G-XXXXXXX" Measurement ID people usually know from their tracking snippet; the API accepts the numeric id and nothing else. Resolve the property yourself from this list instead of asking the user to go and find one, and only ask when two names are genuinely ambiguous. THIS IS NOT EVERY PROPERTY THE GOOGLE ACCOUNT CAN SEE, deliberately: Analytics access is handed out freely, so one login often has Viewer on many different clients' properties, and the user ticks which ones belong to THIS profile. Only ticked properties can be reported on, and any other one is refused by name. An empty list means nothing is ticked yet — say so and point the user at Settings > Connectors > Google Analytics > Manage accounts (or call list_connector_accounts / set_connector_accounts with provider "google_analytics"); never name or guess a property. Read-only, 0 credits. Needs Google Analytics connected (Settings > Connectors > Google Analytics).

list_apple_ads_ad_groups

ChatGPT
Ad groups on the brand’s Apple Ads account — status, the system-computed displayStatus, pricing model, the bid off the ad group’s bidStrategy, and whether Search Match (Apple’s automated keyword matching) is on. campaignId is OPTIONAL: with it, the ad groups of that one campaign; WITHOUT it, every ad group in the ad account, each row carrying its own campaignId. (Apple’s older API had no account-wide ad-group list and required the campaign id; its Platform API does, and this now uses it.) Read-only, free.

list_apple_ads_ads

ChatGPT
The ads on this Apple Ads account — the serving units that put a creative into an ad group. Each row carries the advertiser status, the rolled-up displayStatus, Apple’s own systemStatus and, when an ad is not serving, systemStatusReasons saying exactly why: AD_APPROVAL_PENDING while Apple reviews it, CREATIVE_PENDING, CREATIVE_LOCALE_INCOMPATIBLE, PRODUCT_PAGE_HIDDEN, PAUSED_BY_USER and more. READ THAT FIELD BEFORE CONCLUDING ANYTHING IS BROKEN — a brand-new ad awaiting Apple’s review is NOT_RUNNING by design, and systemStatusLimitingReasons means it IS running but throttled. Filter by adGroupId, campaignId, creativeId or status, or pass id for one. Read-only, free.

list_apple_ads_advertiser_resources

ChatGPT
The brands and content providers this Apple Ads ORGANIZATION can delegate to an ad account — the ids create_apple_ads_ad_account and update_apple_ads_ad_account need. resourceType CONTENT_PROVIDER returns App Store Connect content providers, each identified by its CPID, which is what links an ad account to App Store advertising; BUSINESS_BRAND returns Apple Maps brands by Brand ID. resourceType is REQUIRED — Apple errors without it. This list is ORGANIZATION-WIDE and is deliberately not scoped to any one ad account, which is why a resource can appear here that no account has claimed yet. Read-only, free.

list_apple_ads_assets

ChatGPT
Images in the Apple Ads asset library with format, orientation, dimensions and eligibility — use it to check whether an uploaded asset has finished processing before referencing it in a creative. Filter by brandId (Apple’s promotedObjectId), promotedObjectType or assetType, or pass id for one. CROPS AND OTHER VARIANTS NEVER APPEAR IN A LIST: Apple omits them from query results, so a variant has to be fetched by its own id. Querying covers both App Store apps and Apple Maps brands even though only Maps assets can be uploaded. Read-only, free.

list_apple_ads_brands

ChatGPT
The Apple Maps brands on this Apple Ads account. A BRAND IS THE ROOT OF EVERY ADS-ON-APPLE-MAPS CAMPAIGN and its id is what three other calls need: it is the promotedObjectId of a BUSINESS_BRAND campaign, the brandId upload_apple_ads_asset uploads against, and the brandId a LOCAL_ADS_SEARCH_CREATIVE carries. A brand must read eligibility ELIGIBLE before Apple will let a campaign use it. YOU CANNOT CREATE A BRAND HERE and that is Apple’s design, not a gap: brands come from Apple Business Connect, and Ads on Apple Maps additionally need the ad account to carry productFeatures BUSINESS_BRAND_MANUAL plus a BUSINESS_BRAND delegation, neither of which any API can grant. AN EMPTY LIST IS THE NORMAL ANSWER FOR AN APP STORE ADVERTISER and does not mean anything is broken — App Store campaigns need no brand at all. Read-only, free.

list_apple_ads_budget_orders

ChatGPT
Budget orders on this Apple Ads account — a spend CEILING SHARED ACROSS CAMPAIGNS, which is how an agency caps a client for a quarter: total spend across every campaign assigned to one cannot exceed its value, however generous the individual daily budgets are. Each row carries the value, the active window, the systemStatus and, when it is blocking, the systemStatusReasons saying why (EXHAUSTED, EXPIRED, CANCELLED). Pass id for one. AN EMPTY LIST IS NORMAL: a budget order is optional, and without one each campaign is capped only by its own dailyBudget. Read-only, free.

list_apple_ads_campaigns

ChatGPT
Read the brand’s connected Apple Ads (Apple Search Ads) campaigns — App Store search ads, on Apple’s Platform API. Each row carries status, the system-computed displayStatus, the daily budget, the bid strategy, the countries and placements it runs in and, when a campaign cannot run, systemStatusReasons stating exactly why. Read-only, free, zero spend risk. Needs Apple Ads connected (Settings > Connectors > Apple Ads): there is no OAuth — Hermoso generates an EC signing key, the user pastes the public half into Apple Ads > Account Settings > API and pastes back clientId / teamId / keyId. To BUILD on this account, use create_apple_ads_campaign / create_apple_ads_ad_group / add_apple_ads_keywords: everything is created PAUSED and only set_apple_ads_status(confirm:true) can arm spend.

list_apple_ads_creatives

ChatGPT
The ad creatives on this Apple Ads account, each with its type, the app and product page it points at, its systemStatus (VALID / INVALID / PENDING) and its eligibility per ad placement. A creative is reusable — one can back ads in several ad groups and campaigns. Filter by creativeType, systemStatus, a name prefix or eligibility, pass id for a single one, or includeDeleted:true to see deleted records (Apple excludes them by default). AN EMPTY LIST IS NORMAL on an account that only runs classic Search Results campaigns: those need no creative at all, because Apple renders the App Store product page itself. Read-only, free.

list_apple_ads_keywords

ChatGPT
Targeting or negative keywords with their match type, status and bid. THE TWO RESOURCES SCOPE DIFFERENTLY and Apple enforces it: TARGETING keywords (the default) need campaignId OR adGroupId — campaignId alone now returns every targeting keyword in the campaign, which Apple’s older API had no way to ask for. NEGATIVE keywords (negative:true) always need one of the two as well, and Apple splits them by LEVEL: campaignId alone returns the CAMPAIGN-level negatives that suppress the whole campaign (scope ‘campaign’, the default), scope ‘adgroup’ returns every AD-GROUP-level negative across that campaign in one call, and naming an adGroupId returns just that ad group’s. The reply states which level it counted, because the row shapes are identical and the number means different things. There is no account-wide keyword list. Apple allows up to 5000 keywords per ad group. Read-only, free.

list_apple_ads_location_groups

ChatGPT
The location groups on this Apple Ads account — named sets of a brand’s shops that an ad group targets. Each row carries its type (STATIC or DYNAMIC), Apple’s systemStatus and groupTotal, the number of locations currently in it. READ groupTotal BEFORE TARGETING A GROUP: a DYNAMIC group sits at PENDING while Apple evaluates its rules, and a group that evaluated to ZERO is Apple’s one silent failure here — it reports no error, so a rule spelled "CA" instead of "California" builds an empty group that looks fine. Soft-deleted groups are excluded unless you pass includeDeleted. There is no filter for "which groups contain this location": query by brandId and inspect membership. Read-only, free.

list_apple_ads_locations

ChatGPT
The physical business locations behind an Apple Maps brand — the shops, restaurants or service centres an Ads-on-Apple-Maps campaign promotes. Filter by brandId (do: without it Apple returns every location across every brand, which can be a very large page), plus status, eligibility or address. LOCATIONS ARE READ-ONLY THROUGH THIS API BY APPLE’S DESIGN: Apple Business Connect creates and maintains them, so a missing shop, a wrong address or a closure is fixed there and appears here afterwards — there is no create, update or delete to look for. Only a location with status OPEN and eligibility ELIGIBLE is targetable. You do not target locations directly: collect the ids you want, put them in a location group with create_apple_ads_location_group, then point an ad group at that group. Read-only, free.

list_apple_ads_orgs

ChatGPT
The Apple Ads ad accounts these credentials can act as, with the roles held on each, plus the organization’s currency, time zone and payment model. NAMING: Apple’s older API called these “organizations” (campaign groups) and its Platform API calls them AD ACCOUNTS — the id is the same number, and orgId and adAccountId on each row are equal. One login can cover several: an agency managing multiple clients has one per client. Reading this does NOT switch account: Apple Ads is pinned to the ONE chosen when the connection was made, so an agent can never act as another client’s. To use a different one, pick it in Settings > Connectors > Apple Ads > Manage accounts, with set_connector_accounts, or by reconnecting. A payment model of null is worth reporting: Apple states that without one, campaigns cannot run. Read-only, free.

list_apple_ads_product_pages

ChatGPT
The App Store product pages this ad account can advertise — the post-tap destination a creative points at. Every app has exactly one DEFAULT product page (its standard listing, needing no setup) plus any CUSTOM PRODUCT PAGES the developer built in App Store Connect, each with its own productPageId (a UUID, not an integer) that a CUSTOM_PRODUCT_PAGE creative requires. Filter by adamId. Pass localeDetails:true to read the per-language content instead — appName, subtitle, promotional text and the screenshots and preview videos by device class — for a custom page (productPageId) or for the app’s default listing (adamId); Hermoso picks the right one of Apple’s two endpoints for you. READ-ONLY BY DESIGN: product pages are authored in App Store Connect, not through any API, and a page you just published appears here only after a short propagation delay. Free.

list_apple_ads_recommendations

ChatGPT
Apple’s own optimisation engine for this account: campaigns it thinks are budget-capped, and campaigns whose target CPA it thinks should move. type:"daily_budget" returns DAILYCAP recommendations, each carrying the current and suggested budget plus the historical installs/taps/spend behind it AND Apple’s projection if you raise it; type:"target_cpa" returns TCPA recommendations, which Apple produces ONLY for campaigns on a Maximize Conversions bid strategy — a MANUAL_CPT campaign will never have one. Defaults to state AVAILABLE because that is the only actionable state: APPLIED and DISMISSED are terminal and can never be re-actioned (pass state:"ALL" to see the history). Apple names only two recommendation categories with endpoints; its enum also mentions keyword and bid categories but this API version publishes no way to query or act on those. Read-only, free — reading changes nothing.

list_apple_ads_supported_languages

ChatGPT
Every country and region Apple Ads serves, with the languages supported in each market. Use it to VALIDATE A LOCALE BEFORE setting it on an Apple Maps creative’s localizedText or defaultLocale, or on an ad group — a locale a market does not support makes the creative INVALID after it is built, which surfaces as a serving failure long after the mistake was made. Also the definitive answer to "can we advertise in this market and in what language" when expanding a campaign. Read-only, free.

list_applovin_ads_assets

ChatGPT
Assets on the AppLovin account: id, type (VID_LONG_P, VID_SHORT_P, IMG_INTER_P, IMG_BANNER, HOSTED_HTML), review status and rejection reasons. Read-only, free. Needs the Campaign Management key, which exists once AppLovin enables API access on the account; without it the answer says how to get it.

list_applovin_ads_campaigns

ChatGPT
The brand’s AppLovin Ads campaigns: id, name, LIVE/PAUSED, daily budget, goal, countries, landing page. Rename or edit one with update_applovin_ads_campaign; pause or go live with set_applovin_ads_status. Read-only, free. Needs the Campaign Management key, which exists once AppLovin enables API access on the account; without it the answer says how to get it.

list_applovin_ads_catalogs

ChatGPT
The AppLovin account’s product catalogs and their variant sets: the catalogId and variantSetId a dynamic-ads campaign (create_applovin_ads_campaign) needs. Website accounts. Read-only, free. Needs the Campaign Management key, which exists once AppLovin enables API access on the account; without it the answer says how to get it.

list_applovin_ads_creative_sets

ChatGPT
AppLovin creative sets with their assets and review status, for one campaign (campaignId) or the whole account. Read-only, free. Needs the Campaign Management key, which exists once AppLovin enables API access on the account; without it the answer says how to get it.

list_bing_webmaster_fetched_urls

ChatGPT
The URLs fetched on demand as Bingbot for this site, or one of them in detail (pass url) including what Bingbot actually received. This is how you see a page through the CRAWLER's eyes rather than a browser's — a robots block, a redirect chain or a JS-only page all look fine in a browser and wrong here. An empty list means nothing has been fetched on demand yet, which is a real answer and not a failure. Read-only, 0 credits.

list_bing_webmaster_sitemaps

ChatGPT
The sitemaps registered for this site — Bing calls them FEEDS — with the status, URL count, file size and last-crawled date Bing holds for each. This is the read to make BEFORE submitting or removing one, because both of those are addressed by the sitemap's full URL exactly as Bing stores it. Pass feedUrl to expand a sitemap INDEX into its child sitemaps (Microsoft's GetFeedDetails); an empty answer for a plain non-index sitemap is a real answer, not a failure. Read-only, 0 credits.

list_bing_webmaster_sites

ChatGPT
The Bing Webmaster Tools sites SHARED WITH THIS PROFILE, each exactly as Bing holds it (e.g. "https://example.com"). CALL THIS FIRST — every other Bing tool takes that exact string. THIS IS NOT EVERY SITE THE CONNECTION CAN REACH: Microsoft issues one key per USER covering every site that Bing account verified, so the user ticks which belong to THIS profile and any other one is refused by name. An empty list means nothing is ticked yet — point the user at Settings > Connectors > Bing Webmaster Tools > Manage accounts (or call set_connector_accounts with provider "bing_webmaster") and never name or guess a site. A site flagged not verified will refuse every later call. Read-only, 0 credits.

list_bluesky_convos

ChatGPT
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.

list_bluesky_posts

ChatGPT
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.

list_brands

ChatGPT
List every profile on this account (id + name; a profile is a brand, a creator or a personal workspace) and which one this connection acts on, PLUS any profile another account shared with you. Call this, then use_brand to switch; a SHARED profile switches the same way (pass its name or the profile id printed here). If a profile looks empty when the app shows it full, you are acting on a different one: call this first. Read-only, free.

list_business_attributes

ChatGPT
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.

list_business_categories

ChatGPT
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.

list_business_locations

ChatGPT
List the Google Business Profile listings SHARED WITH THIS PROFILE — id, title, address, website and Maps link. These are the only listings anything here can post to or read: one Google login often manages several businesses (an agency manages its clients’), and the user ticks which of them belong to this profile. Call this before posting whenever more than one is shared and let the USER pick: a Post on the wrong storefront is a public mistake Hermoso will not make for them. If nothing is shared, ask the user to choose — list_connector_accounts("google_business") then set_connector_accounts — and never name or guess a listing. Read-only, 0 credits. Needs Google Business Profile connected (Settings > Connectors > Google Business Profile).

list_connector_accounts

ChatGPT
Show every identity a connected account can act as, and which ones this PROFILE is currently allowed to use — Facebook Pages + Instagram + Meta ad accounts, Google Ads customers, LinkedIn company Pages (and the personal profile), Pinterest ad accounts, Microsoft Advertising accounts. One person often administers several; only the ticked ones can be posted to or spent from. Call this before set_connector_accounts, and let the USER pick — never guess. Providers: tiktok, x, youtube, threads, bluesky, telegram, reddit, pinterest, instagram, meta, google_ads, linkedin, pinterest_ads, linkedin_ads, reddit_ads, apple_ads, microsoft_ads, google_business, google_analytics, snapchat_ads, x_ads, tiktok_ads, google_tag_manager, google_search_console, bing_webmaster. Read-only, free.

list_connectors

ChatGPT
List the third-party accounts connected to this workspace (Meta, Google Ads, Google Drive/Sheets/Docs, YouTube, LinkedIn, OneDrive, Slack, …) — provider, status and the connected account label — PLUS which providers are available to connect. IT ALSO FLAGS A CONNECTION WHOSE PERMISSIONS ARE OUT OF DATE: a provider writes its granted permission set into the token at consent time, so a connection authorized before a permission was approved does not carry it and never will — those calls are refused by the provider and no retry or wait can change it. Check this FIRST when a connected provider starts refusing things. The fix is a browser: the user reconnects under Workspace > Connectors. Read-only, free.

list_creators

ChatGPT
List this workspace’s SAVED CREATORS — the reusable on-camera cast (AI creators made here, a person pulled from a social profile, a consented photo upload). Read-only, FREE. Each entry gives the name, the PORTRAIT URL, where the portrait came from and whether a real person’s likeness consent is on file, how many extra pose plates exist, and any chosen or cloned voice. TO PUT ONE IN A FINISHED AD, pass their id or name as render_ad’s creator — that casts them for the whole spot (and skips the character-portrait render, so it costs less than not casting anyone). THE PORTRAIT URL IS THE REUSE HANDLE for the raw lanes — pass it as generate_avatar’s image (a talking clip of them), generate_video’s refImage (they star in the scene), recast_motion’s image (they perform a reference clip’s motion), or generate_image’s refImages. CALL THIS BEFORE OFFERING TO GENERATE A NEW PERSON: re-casting somebody the workspace already has keeps the SAME face across every ad, while a fresh person costs credits and breaks that continuity. An empty answer means the workspace genuinely has no cast yet — say so and offer generate_avatar / save_creator, never invent a roster.

list_dm_automations

ChatGPT
The brand's DM automations on Instagram, Facebook (Page comments, ads, Messenger) and X, each with its stats, the recent send log and the connected accounts per channel. Pass channel to narrow, id for one rule's log. Free.

list_drive_files

ChatGPT
List the Google Drive files & folders Hermoso can reach: the ones it created plus any the user picked with the Google file picker in the app (drive.file, never their whole Drive). This is how you find a picked file's id. Filter by query (name contains …), folderId, or onlyFolders:true; paginate with pageToken. Read-only. A FOLDER: picking a folder hands over the folder alone, not its files, so a folder can list empty or partly; the reply then carries pickUrl, an app link that opens the picker inside that folder (Cmd+A / Ctrl+A selects every file and subfolder). Give the user that exact link, then list again. recursive:true walks every subfolder Hermoso can see (each row has its path) and reports picked files whose folder it cannot see.

list_errors

ChatGPT
The errors actually recorded against this workspace, GROUPED by fingerprint — the same failure at the same call site is one row with a hit count and first/last seen, sorted defects-first. Each row says whose side it is: ours (a defect worth fixing), user (a refusal we deliberately authored, e.g. not-connected or out-of-credits), or unknown (a vendor 4xx we cannot attribute — never guessed). Free text, tokens, emails and creative are redacted before anything is stored, so an input echo shows shapes and lengths, not content. Filter by surface (http/mcp/agent/job/client) or kind. Read-only, 0 credits. Scoped to your own workspace; an operator whose client carries the admin key sees every account.

list_google_ads_campaigns

ChatGPT
Read the connected Google Ads account(s). Call with NO customerId to list the accessible accounts (customerId + name + currency) — do this first to pick a target. Call WITH customerId to list that account’s campaigns (id, name, status, daily budget, channel) plus performance metrics (impressions, clicks, CTR, avg CPC, cost, conversions). Date window: datePreset (LAST_7_DAYS | LAST_30_DAYS | TODAY | THIS_MONTH | LAST_90_DAYS …) or since+until (YYYY-MM-DD). Read-only, free. Needs Google Ads connected (Settings > Connectors > Google Ads).

list_google_ads_conversion_actions

ChatGPT
List the conversion actions on a Google Ads account and say plainly whether smart bidding can work there. Call this BEFORE proposing MAXIMIZE_CONVERSIONS / MAXIMIZE_CONVERSION_VALUE / TARGET_CPA / TARGET_ROAS or any Performance Max campaign: an account with no ENABLED conversion action that counts toward "conversions" cannot optimise on any of them, and the campaign would spend its budget without ever learning. Shows each action’s status, type, category, counting type, and whether it counts toward "conversions". Read-only, free.

list_google_business_posts

ChatGPT
List the Posts currently on the brand’s Google Business Profile listing — text, topic type, state (LIVE / PROCESSING / REJECTED / SCHEDULED / RECURRING), button and timestamps. Use it to see what is already showing before writing another, or to get the id of one to remove. Read-only, 0 credits. Needs Google Business Profile connected.

list_google_business_questions

ChatGPT
The questions the public has asked on the brand’s Google Business Profile listing, with the answers so far and how many people upvoted each question. Unanswered questions sit publicly on the listing and are read as "this business does not respond" — the reply names the ones with no answer at all. Read-only, 0 credits. Needs Google Business Profile connected and the project approved.

list_google_business_reviews

ChatGPT
The reviews customers have left on the brand’s Google Business Profile listing — star rating, reviewer, the text, when it landed, and whether the business has replied. For a local business this is the highest-leverage surface there is: an unanswered review sits on the listing next to the ad you paid for. The reply says which ones have NO answer yet, so you can work the list rather than read it. Google reports the listing’s own average rating and total review count alongside the page — use those for "how are we doing", never a mean you computed over one page. An empty page is an empty PAGE, not proof the listing has no reviews. Read-only, 0 credits. Needs Google Business Profile connected AND the project approved for Google’s Business Profile APIs (a pending access request, not a setting — the error says so).

list_hooks

ChatGPT
The curated menu of VISUAL scroll-stop HOOKS (how an ad opens) and SETTINGS (where it is staged) that plan_ad and render_ad accept, PLUS this brand's measured traction per hook. Call it before planning an ad to pick a hook deliberately instead of letting the model improvise one, and call it after publishing to see which ones are actually landing. Three things it will not do: it never recommends a hook from thin data — a verdict is SUPPRESSED below 5 measured posts and the reason is stated; it never compares across channels; and it reports hooks you have NEVER TRIED as a fact, not as advice, because 'you haven't tried this' is an observation and 'you should' would be a verdict drawn from zero data. A hook marked unusable in this brief says WHY (an on-screen-text hook cannot ride an authentic/UGC render, which carries zero on-screen text). Read-only, 0 credits.

list_hubspot_contacts

ChatGPT
Contacts from the brand's OWN HubSpot CRM, newest first — email, name, lifecycle stage, lead status, created date, original source — or a search by email / first name / last name (whole-token match, wildcards allowed). Read-only, free. Needs HubSpot connected (Settings > Connectors > HubSpot).

list_inbox

ChatGPT
EVERYTHING PEOPLE SAID TO THIS BRAND, across every connected channel, in one list: Facebook and Instagram comments, Threads replies and mentions, YouTube and Reddit 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. 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. DMs are read one conversation at a time and fold to ONE item — the newest thing the other person said — so pass conversationId (from list_meta_conversations, list_bluesky_convos or list_x_dms) to include them. 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.

list_instagram_collab_invites

ChatGPT
Posts where a creator tagged this Instagram account as a COLLABORATOR and is waiting for an answer. The post appears on the brand’s profile only once the brand accepts, and Instagram sends no notification here, so this list is the only way to see them. Answer each with respond_instagram_collab_invite. Read-only, free. Instagram allows 300 reads per account per day.

list_instagram_collab_media

ChatGPT
Every collaborative post this Instagram account is a co-author on — who posted it, and the COMBINED engagement across all co-authors (total likes and comments, plus saves, shares and reposts where Instagram provides them). Read-only, free.

list_instagram_dm_automations

ChatGPT
The brand's Instagram DM automations (comment keyword -> DM, DM keyword, story reply, ig.me ref), each with its stats, plus the recent send log and the connected Instagram accounts. Pass id for one rule's log. Free.

list_instagram_media

ChatGPT
The connected Instagram Business account’s own recent media — id, caption, media type (feed / reel / story-era), permalink, timestamp, like and comment counts. This is where the media id every other Instagram tool needs comes from: resolve “my latest reel” yourself instead of asking the user for a link, then pass the id to meta_post_insights. Read-only, 0 credits.

list_instagram_shopping_catalogs

ChatGPT
Whether this Instagram account can tag products at all, and which catalogs its SHOP can tag from. CALL THIS FIRST: product tagging needs an APPROVED INSTAGRAM SHOP, and if the account does not have one, tagging fails AFTER the photo is already uploaded. The reply says which of three things is true — eligible, not eligible (a Commerce Manager approval nothing in Hermoso can grant, and not a sign anything is broken), or "could not tell", which is NOT the same as not eligible. AN EMPTY CATALOG LIST IS NOT AN EMPTY CATALOG: Instagram reaches a catalog through the account's SHOP, while list_meta_catalogs reads the business PORTFOLIO — a merchant can have a full catalog there and nothing available here until the shop is approved. Read-only, 0 credits. Whether Meta has approved Hermoso's app for the permission this needs is read LIVE from Meta: when it has not, list_connectors says so under Meta and this tool's refusal says Meta hasn't approved Hermoso for it yet. That is Meta's decision about Hermoso, not the user's connection, so never tell them to reconnect for it.

list_intercom_conversations

ChatGPT
Support conversations from the brand's OWN Intercom inbox, newest activity first: subject, state, whether the customer is waiting, assignee, customer email, channel, tags and the first message. One Intercom workspace can serve several products, so every filter below combines with the others. view: open (default), unassigned, waiting (customer waiting on a reply), snoozed, closed, recent or all. days bounds it by last update (default 30), or use updatedAfter / updatedBefore / createdAfter / createdBefore (dates). text matches words in the first message; email is the customer's exact email, customer is part of their name or email; contactId; assigneeId (a teammate) or teamId (ids from list_intercom_teammates); tagId or tagName; channel (conversation is the Messenger, or email, whatsapp, sms and so on); priority true or false; unread true. app is the product the customer wrote from (their contact's current_app, case-insensitive, for example Hermoso); contactAttributes and attributes match other custom attributes of the customer or of the conversation. Intercom's search cannot filter on custom attributes or app, so those are matched on the pages read and the answer says how many it read. Pass cursor from the last answer for the next page. Read-only, free. Needs Intercom connected (Settings > Connectors > Intercom).

list_intercom_tags

ChatGPT
The tags in the brand's Intercom workspace (id and name), for tag_intercom_conversation or to filter list_intercom_conversations. Read-only, free. Needs Intercom connected (Settings > Connectors > Intercom).

list_intercom_teammates

ChatGPT
The brand's Intercom teammates and teams with their ids, for assigning a conversation or filtering the list by assignee. Read-only, free. Needs Intercom connected (Settings > Connectors > Intercom).

list_jobs

ChatGPT
List the most recent render jobs + how many are currently running, so you can report on or resume in-flight work.

list_library

ChatGPT
Browse this workspace's Library — every image/video generated in the Studio, newest first (the same Library the web app shows). Returns served URLs you can open directly or hand to fetch_asset for a download link, plus each asset's id (L0, L1…, which plan_ad and render_ad inserts take), kind, model, and age. Free, read-only.

list_linkedin_ads_campaigns

ChatGPT
Read the LinkedIn ad accounts this connection can reach, and — with adAccountId — that account’s campaign groups and campaigns: name, status, objective, budgets, and LinkedIn’s own servingStatuses, which explain WHY something is not delivering (billing hold, start-date hold, parent-status hold). LinkedIn’s Advertising API is an approval-gated product, and on its Development tier each ad account must ALSO be mapped to the app in LinkedIn’s Developer Portal — so if nothing is reachable, say that rather than implying the user has no ad account. Read-only, free.

list_linkedin_comments

ChatGPT
Read the comments people have left on one of your LinkedIn company Page's posts. Community Management surface, and it needs its OWN connection: LinkedIn grants the comment scopes on a different app from the one that posts, so connecting LinkedIn does NOT connect this — the card is “LinkedIn Community Management” in Settings > Connectors. Pass the post's urn, which is what post_to_linkedin_page returns. A post nobody has commented on comes back as an EMPTY LIST with a note, never an error: LinkedIn answers 404 for a post with no social activity, and that means “no comments yet”, not “no such post”. Each comment carries its text, author, time, like and reply counts, and isOurs so your Page's own replies are distinguishable from other people's comments; fromOthers counts the ones actually awaiting an answer. The connected member must hold an ADMINISTRATOR role on the Page.

list_linkedin_conversion_rules

ChatGPT
List the conversion rules on a LinkedIn ad account, including ones SHARED from other accounts in the same Business Manager. Use it to find the conversionId send_linkedin_conversions needs, and to check whether a rule can actually receive API events — only a rule whose conversionMethod is CONVERSIONS_API and which is enabled can, and a rule built for the Insight Tag cannot. Zero rules genuinely means none exist; say that rather than implying a failure. Read-only, free.

list_linkedin_events

ChatGPT
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.

list_linkedin_lead_events

ChatGPT
The lead events LinkedIn has PUSHED to Hermoso for this brand (new lead / deleted lead, with the form and the lead id), newest first. Empty means none have arrived, not that none exist — list_linkedin_leads reads every lead regardless, and subscribe_linkedin_leads is what starts delivery. Read a lead’s answers with get_linkedin_lead. Read-only, free.

list_linkedin_lead_forms

ChatGPT
The LEAD GEN FORMS a LinkedIn company Page or ad account owns — id, name, state, version and the fields each one asks for (firstName, email, company …). Forms are created in Campaign Manager or on the Page; this API reads them and cannot create one. If it answers that the connection must be reconnected, say exactly that: the lead-sync permission is granted at authorise time. Read-only, free.

list_linkedin_lead_subscriptions

ChatGPT
The lead notification webhooks registered on a LinkedIn Page or ad account, with the id delete_linkedin_lead_subscription takes. Read-only, free.

list_linkedin_leads

ChatGPT
The LEADS a LinkedIn lead gen form collected — every response with its answers keyed by field (firstName, lastName, email, company, …), the campaign and creative that produced it, the consents ticked, and whether it was a test lead. Newest first. Filter by formId, a since/until window (ISO date or epoch ms — LinkedIn takes epoch), or testLeadsOnly. THIS IS PERSONAL DATA: show it to the user, hand it to the CRM they name, never repeat it into a post or an unrelated tool. Pass start for the next page. Read-only, free.

list_linkedin_pages

ChatGPT
List the LinkedIn COMPANY PAGES the connected account administers — id, name and the role held on each. ALWAYS call this before post_to_linkedin_page when there is more than one Page: publishing to the wrong company Page is a public mistake and Hermoso never chooses for the user. If it comes back empty, the account holds no Page admin role, or LinkedIn has not granted this app the organization scopes — say that plainly rather than guessing an id. Read-only, free.

list_memory

ChatGPT
List the durable facts & preferences saved in this workspace’s Memory (what the studio remembers about the brand, audience, taste, and do/don’t rules) — the same Memory the web app shows. These shape every future ad. Read-only, free.

list_merchant_accounts

ChatGPT
List the Google Merchant Center accounts this brand's connected Google account can reach. EVERY other Merchant tool needs the merchantCenterId this returns, and an agency often has several, so never guess one — ask the user which store. Read-only and free. If it is refused, the Merchant Center permission has not been granted yet; that is not a broken connection and reconnecting will not change it.

list_merchant_data_sources

ChatGPT
List the data sources (feeds) on a Merchant Center account and say which of them can actually take a product write. Do this BEFORE creating or deleting a product: writes go into a data source, and Google only accepts them into an API-input product feed — a file feed, the feed Merchant Center's own UI creates, and an autofeed all list here and all REFUSE writes, so picking the first row would pick a feed that cannot be written to. Needs merchantCenterId from list_merchant_accounts. Read-only and free.

list_merchant_inventory

ChatGPT
The per-store and per-region price and availability overrides on one Shopping product. An override REPLACES the product's own price and availability for that store or region; where there is no override the product-level values apply, so an empty result is the normal answer and does NOT mean the product is missing. Needs the product's offerId plus its contentLanguage and feedLabel, because in the Merchant API those three together ARE the product's address rather than optional filters. Read-only, free.

list_merchant_products

ChatGPT
Read the products in a Merchant Center feed — what the merchant actually offers, which is what Shopping and retail Performance Max campaigns serve. Needs merchantCenterId from list_merchant_accounts. Read-only and free.

list_merchant_promotions

ChatGPT
The sale and discount promotions on a Merchant Center account, with each one's PER-DESTINATION status and any issues Google has raised against it. GOOGLE VALIDATES PROMOTIONS ASYNCHRONOUSLY, so 'created' and 'running' are different states and only destinationStatuses tells them apart — never report a sale as live off a successful create. Read-only, 0 credits. Needs Google Ads connected (Merchant Center rides the same connection).

list_messenger_marketing_campaigns

ChatGPT
The marketing-message campaigns on an ad account: name, id, budgets, schedule. Free.

list_messenger_subscribers

ChatGPT
The people who OPTED IN to marketing messages from a Facebook Page — their subscription tokens, the only address a paid Messenger marketing message can go to (there is no "message everyone"). Also reports whether this Meta connection carries the marketing_messages_messenger permission; a connection made before it was added must be reconnected. Free.

list_meta_ads

ChatGPT
Read the EXISTING campaigns, ad sets, or ads on a connected Meta ad account — id, name, status, budget, objective. Pass adAccountId (from list_meta_pages) and level (campaign|adset|ad). Scope to a parent with campaignId (-> its ad sets/ads) or adsetId (-> its ads), and filter by status (ACTIVE/PAUSED/…). Read-only — use it to inspect an account before editing/deleting, or to answer "what’s running?".

list_meta_audiences

ChatGPT
List the custom audiences and lookalikes on a connected Meta ad account — id, name, type, approximate size, and whether Meta says it is ready to target. Call it before create_meta_audience (so you never build a duplicate) and before targeting one: the ids go straight into create_meta_ad’s targeting.customAudiences / excludedCustomAudiences. Read-only, free.

list_meta_catalog_products

ChatGPT
A page of the actual PRODUCTS in a catalog — name, retailer id, price, availability, image and, most importantly, Meta’s REVIEW STATUS for each. A REJECTED product stays in the catalog, is counted by the catalog’s catalog.product_count field, and will never be shown in an ad — so this is how you find out why a catalog ad is serving less than the merchant expects. Use it to confirm a catalog really holds what the merchant thinks before pointing money at it. This is a PAGE, not a count: quote the catalog.product_count list_meta_catalogs reports for the total. Read-only, 0 credits.

list_meta_catalogs

ChatGPT
The PRODUCT CATALOGS this Meta login can advertise from — id, name, how many products each holds, and which business portfolio owns it, WITH THAT PORTFOLIO’S NUMERIC ID. It also lists every business portfolio this login administers (portfolios), whether or not it holds a catalog yet — so this is where the businessId create_meta_catalog needs comes from, and the two tools chain with no trip to Business Manager. A catalog is the input to Advantage+ catalog ads (dynamic product ads): one campaign that shows each person the products they are most likely to buy, with the image, name and price taken from the catalog rather than from a render. START HERE for any "advertise my products" ask, then list_meta_product_sets to pick what to advertise. AN EMPTY LIST IS A FACT ABOUT WHAT THIS LOGIN ADMINISTERS, NEVER ABOUT THE MERCHANT: Meta serves catalogs off the BUSINESS PORTFOLIO, so a full Shopify-synced catalog is invisible here if the connected user is not an admin of the portfolio that owns it — read the note and say that, never "you have no catalog". A portfolio that could not be read is reported as unreadable rather than dropped. Read-only, 0 credits.

list_meta_comments

ChatGPT
Read the comments under a Facebook Page post or Instagram media object — customer questions, objections and the exact language real people use about the product. Good raw material for ad copy, and the first step before replying or moderating. REPLIES: a reply is a comment ON a comment, and its id exists only under its PARENT — it is never returned by the post. Each row says how many replies it has; to read them (and to get the id reply_to_meta_comment / hide_meta_comment / unhide_meta_comment / delete_meta_comment need), call this tool again with postId set to that COMMENT id.

list_meta_conversations

ChatGPT
The DM threads on the brand’s Facebook Page and its Instagram account — who wrote, when they last wrote, how many messages and how many are unread. THIS IS WHERE THE BUYING QUESTION GETS ASKED. IT READS THE INBOX AND THE MESSAGE REQUESTS BY DEFAULT (Meta’s "other" folder), which is where a DM from someone who does NOT follow the account lands — every thread says which of the two it came from, because a message request is not an accepted conversation and whoever answers it should know that first. Omit platform to read BOTH surfaces (one question to a person, two edges to Meta); name one to narrow. AN EMPTY LIST IS NEVER "nobody has messaged you" — the reply says so and names the MEASURED causes, including one the account holder cannot fix: Instagram Direct on this edge needs an app-level Instagram messaging capability, and without it Meta answers with an empty list rather than an error. Never tell anyone to go and switch something on inside the Instagram app — there is no such setting on a professional account. Read one thread with read_meta_conversation. Read-only, 0 credits.

list_meta_lead_forms

ChatGPT
List the INSTANT LEAD FORMS on a connected Facebook Page — id, name, status, how many leads each has collected and what each one asks. This is where the formId every other lead tool needs comes from, and calling it before create_meta_lead_form is how you avoid building a duplicate. Read-only, free. NEEDS the pages_manage_ads permission, which Hermoso’s Meta consent screen asks for. If Meta answers "Requires pages_manage_ads", the user must RECONNECT Meta under Settings > Connectors > Meta — a connection made before that permission was added cannot gain it by retrying. Call the tool rather than pre-refusing: any refusal comes from Meta and names the one thing that fixes it.

list_meta_pages

ChatGPT
List the Facebook Pages (with any linked Instagram business account) and ad accounts on the connected Meta account — use before post_to_meta / create_meta_campaign to pick the target. Requires the user to have connected Meta (Settings > Connectors > Meta); returns a connect hint if not.

list_meta_partnership_creators

ChatGPT
The creators this brand has set up for PARTNERSHIP ADS — ads that run from a CREATOR’s handle instead of the brand’s, which is what a paid collaboration looks like in feed. TWO SEPARATE LISTS come back and NEITHER IMPLIES THE OTHER: the AD-PERMISSION list is whose content this profile may run as an advert, and the TAG-APPROVAL list is who may tag this brand as a paid partner on their own organic post. Adding a creator to one does nothing for the other, and that is the mistake that makes a partnership ad fail for a reason Meta’s error does not name. Every row carries the status Meta reports VERBATIM. Meta’s own wording on the wire is prose, not the enum its docs publish — measured 2026-08-25, a fresh request reads “Pending Approval” and a revoked one reads “Canceled” (Partnership Ads Hub calls the same two rows “Request sent” and “Inactive”). PENDING means the creator has not accepted yet and this profile cannot advertise their content until they do. REVOKING DOES NOT REMOVE THE ROW: Meta keeps it and flips the status, so read the status rather than the presence of a row. An unrecognised status is passed through as Meta’s own word, never blanked. A list that could not be READ says exactly that; it is never rendered as "this brand has no creators". THE TWO LISTS ANSWER DIFFERENTLY, and Meta is the reason: the ad-permission list can be ENUMERATED, and the tag-approval list CANNOT — Meta requires user_ids on that edge and returns "a list of approved creators, filtered by the user IDs provided", i.e. it CHECKS the people you name and cannot report the rest. So pass creatorIds to ask about specific creators; anyone already on the ad-permission list is checked for you, which is what makes "on one list but not the other" answerable in a single call. With nobody to check, the reply says Meta publishes no way to list that edge rather than reporting a failed read. Read-only, 0 credits.

list_meta_pixels

ChatGPT
List the META PIXELS on one of the brand’s ad accounts — id, name, when it was created, and WHEN IT LAST FIRED. This is where the pixelId every conversion tool needs comes from: create_meta_ad takes it (with conversionEvent) to optimise an ad set for OFFSITE_CONVERSIONS instead of link clicks, and create_meta_audience needs it to build a website retargeting audience. Without this tool that id could only be read off a screen in Events Manager. READ lastFiredAt BEFORE YOU TRUST A PIXEL: one that has NEVER FIRED is not installed on the site, so an ad optimising against it will spend and never learn. Pass includeCode:true to get the <script> snippet for installation (it is long, so it is off by default). Read-only, free.

list_meta_posts

ChatGPT
List the connected Facebook Page's or Instagram account's OWN existing posts — id, caption, permalink, publish date and format. THIS IS THE TOOL THAT GETS YOU THE postId every other Meta read needs: meta_post_insights, list_meta_comments and edit_facebook_post / delete_meta_post / set_facebook_video_cover all require one. Use it for "how did our last few posts do", to find a post the user describes loosely, or before backfill_posts. Pass target:'instagram' for the linked IG account (Stories are excluded — Meta's media edge does not return them); Facebook hides unpublished drafts unless you ask for them. Only ever reads a Page the brand has connected. Read-only, 0 credits.

list_meta_product_sets

ChatGPT
The PRODUCT SETS inside one catalog — a set is a slice of the catalog ("summer dresses", "under $50", "everything"), and it is what an AD SET actually advertises. A catalog id is NOT a valid ad-set binding, so this is the list you pick from before building a catalog ad: pass the chosen id as productSetId on create_meta_adset or create_meta_ad. Meta normally auto-creates an "all products" set with every catalog, so an empty list points at the catalog rather than at the request. A set holding ZERO products binds without error and then serves nothing — the ad runs, spends and shows no product — so check the counts. Read-only, 0 credits.

list_meta_webhook_events

ChatGPT
The Page events Meta has PUSHED to Hermoso — new comments, mentions, lead-form submissions and inbound Messenger/Instagram DMs — each with the moment it arrived. This is the difference between a brand learning about a comment when someone thinks to look and learning about it when it happens. Scoped to the Pages this profile can actually see, so it never shows another account’s activity. AN EMPTY FEED IS AMBIGUOUS AND THE REPLY SAYS SO: nothing has happened, or the Page is not subscribed (check meta_webhook_status — an unsubscribed Page is silent and looks exactly like a quiet one), or Hermoso restarted and rebuilt the feed from its durable mirror, which keeps recent deliveries rather than all history. Read-only, 0 credits.

list_microsoft_ads_audiences

ChatGPT
List a Microsoft Advertising account's audiences — by default the CUSTOMER LISTS (Customer Match), with each row's membership duration and its current Search and Audience-network sizes. TWO MICROSOFT CAVEATS THAT LOOK LIKE FAILURES AND ARE NOT: a size is nil or empty for UP TO 48 HOURS while a list is being built, and Microsoft will not use an audience of fewer than 300 people at all — so never report a fresh list's 0 as a failed upload. Pass types[] to ask for other audience types. Read-only, free, 0 credits.

list_microsoft_ads_campaigns

ChatGPT
Read the brand’s connected Microsoft Advertising (Bing Ads) account(s). Call with NO accountId to list the accounts shared with this profile — do this first to pick a target. Call WITH accountId to list that account’s campaigns (id, name, status, daily budget, campaign type, and whether the budget is SHARED). Microsoft statuses are Active / Paused — never Google’s ENABLED — and Microsoft also sets BudgetPaused, BudgetAndManualPaused and Suspended on its own, so report the status you read rather than assuming a paused campaign was paused by a person. Read-only, free. Needs Microsoft Advertising connected (Settings > Connectors > Microsoft Advertising).

list_microsoft_ads_conversion_goals

ChatGPT
The conversion goals on a Microsoft Advertising account — what the account COUNTS as a conversion, and therefore what every conversion-optimised bid strategy on it is optimising toward. Flags which ones are OFFLINE goals: those are the only goals send_microsoft_ads_offline_conversions can post to, and its conversionName must match one of their names EXACTLY, including case and spacing. An account with no goals at all has no conversion tracking — say that plainly rather than reporting an empty list, because it means smart bidding there has nothing to aim at. Read-only, 0 credits.

list_microsoft_ads_profile_targeting

ChatGPT
Read the LinkedIn profile targeting on a Microsoft Advertising campaign: every company, industry, job function, seniority and job title criterion with its bid adjustment and criterion id, read from Microsoft. A profile type Microsoft would not read is named as unread, never reported as none. Read-only, free.

list_microsoft_merchant_catalogs

ChatGPT
List the catalogs inside a Microsoft Merchant Center store. Catalogs logically group products, and a Shopping campaign's product scope points at them. Each row says whether publishing is ENABLED — products in a catalog with publishing off do not serve at all, which is a common and invisible reason a feed appears healthy and delivers nothing. Products go into the store's default catalog unless a call names catalogId. Read-only, free, 0 credits.

list_microsoft_merchant_issues

ChatGPT
Why a Microsoft Merchant Center store's products are NOT serving — the approved / disapproved / expiring counts plus the per-offer disapprovals and warnings with Microsoft's own issue codes. This is the tool to reach for when a Microsoft Shopping campaign is live and showing nothing. TWO CAVEATS THAT LOOK LIKE FAILURES AND ARE NOT, both repeated in the note: Microsoft returns detail rows ONLY for offers that are Disapproved or in Warning, so an EMPTY detail list is the healthy state; and a status change takes up to two hours to reach the summary, so a fresh upload legitimately shows as nothing yet. Read-only, free, 0 credits.

list_microsoft_merchant_products

ChatGPT
Read the product offers in a Microsoft Merchant Center store — the feed a Microsoft Shopping campaign serves from. Pass productId to fetch ONE offer, or omit it to page the store (limit up to 250, then pass the returned pageToken). MICROSOFT'S PRODUCT IDS ARE COMPOSED AND CASE SENSITIVE: the id is channel:contentLanguage:targetCountry:offerId (e.g. Online:en:US:Sku123), not the bare offerId, and a re-cased id addresses nothing — always use the id this tool returned. Read-only, free, 0 credits.

list_mixpanel_cohorts

ChatGPT
THE SAVED COHORTS in the Mixpanel project — each with its id, name, description and CURRENT MEMBER COUNT. Two uses, and both matter for advertising: the counts alone answer "how big is our converted audience", and the id is what mixpanel_profiles needs to read the PEOPLE in one. A cohort hidden in Mixpanel's UI is still listed and flagged rather than dropped, because hidden is a display choice and it is still queryable by id. ONE CALL, and the cheapest thing on this connector — worth calling before mixpanel_profiles rather than guessing an id. Remember Mixpanel allows only 60 QUERIES PER HOUR across its entire Query API (5 concurrent), the tightest budget of any connector here, and this shares it with every other Mixpanel tool. Read-only, 0 credits.

list_mixpanel_events

ChatGPT
The EVENT VOCABULARY of the Mixpanel project — the most common event names over THE LAST 31 DAYS, so a query names something the project actually records rather than a guess. Start here before any other Mixpanel call. Pass event to list THAT event's top property names instead; the two are different questions with different answers, and a failure to read properties is reported AS a failure and never as "this event has no properties". Pass window "today" for Mixpanel's separate today-only endpoint, which carries counts and the percent change from yesterday — that is a DIFFERENT and far narrower question, and on a quiet project (or simply early in the project's own timezone day) it legitimately returns nothing while the project still records dozens of event types, so NEVER read an empty today answer as "this project has no events". 60 queries/hour across the whole Query API (5 concurrent) — the tightest budget of any connector here, so widen a range rather than looping over days. Read-only, 0 credits.

list_onedrive_files

ChatGPT
List files & folders in the user’s OneDrive — the root by default, a folder’s contents (folderId), or a name search (query). onlyFolders:true lists folders only. Paginate with pageToken (the cursor from a previous call). Read-only.

list_openai_ads_audience_operations

ChatGPT
List the membership operations (add, remove, replace) ChatGPT Ads has accepted on one custom audience, each with its status. Use it to find an operation whose id was lost, or to see what is still pending, before get_openai_ads_audience_operation, cancel_openai_ads_audience_operation or resume_openai_ads_audience_operation. Read-only, free. Pages with limit (1-100, default 25) and cursor.

list_openai_ads_audiences

ChatGPT
List the custom audiences on the connected ChatGPT Ads account. Pass intendedUse to see only the ones eligible for exclusion, inclusion or a bid multiplier (inclusion and bid multipliers need roughly 25,000 matched users). Read-only, free.

list_openai_ads_audit_logs

ChatGPT
Who changed what on the ChatGPT Ads account and when: each create/update with its actor and every changed field’s before and after value. Read it first when a campaign starts or stops spending unexpectedly. Filter by campaignId (includes its ad groups and ads), adGroupId, adId, actorId, since/until (date, ISO time or unix seconds). Read-only, 0 credits.

list_openai_ads_business_agents

ChatGPT
The BUSINESS AGENTS on this ChatGPT Ads account — a Business Agent is a branded assistant ChatGPT serves inside an ad (mode business_agent on the campaign): instructions, conversation starters, optional product feeds, tools and a lead form. Shows draft/published status and pending changes. Read-only, 0 credits.

list_openai_ads_campaigns

ChatGPT
Read the brand’s connected ChatGPT Ads account — the ads that appear below ChatGPT answers. Every campaign, ad group and ad row carries servingIssues, OpenAI’s own list of what is blocking delivery (payment method, brand review, budget spent, ad in review, landing page not crawlable, country policy…) with a plain meaning each: null means OpenAI was not asked, [] means it reports no blocker, which is still not a promise of impressions. Call with NO ids to get the ad account itself (its three names: brandName shown in ads, accountName internal to Ads Manager, legalName; currency, status, brand review and business review state) plus its campaigns; with campaignId to list that campaign’s ad groups; with adGroupId to list that ad group’s ads, including each ad’s REVIEW status, which is what decides whether it can ever show. Statuses here are active / paused / archived. Read-only, free, zero spend risk. Needs ChatGPT Ads connected under Settings > Connectors > ChatGPT Ads in the Hermoso app; one connection covers one ad account.

list_openai_ads_conversion_events

ChatGPT
List the conversion event settings on the connected ChatGPT Ads account. Their ids are what a campaign points at (conversionEventSettingIds) so it optimises for CONVERSIONS rather than raw clicks — without one, conversion-optimised bidding has nothing to optimise toward. Read-only, free.

list_openai_ads_feed_uploads

ChatGPT
Recent product-feed UPLOADS across the account with per-upload status, rows accepted / rejected / ads-eligible and OpenAI’s DIAGNOSTICS (code, severity, field and rows affected, e.g. invalid_value on price) — the way to learn why products are not serving. Per-product review status (why ONE product was rejected) is shown only in ChatGPT Ads Manager > Products: OpenAI does not expose it through the API, so say that rather than guessing. Read-only, 0 credits.

list_openai_ads_feeds

ChatGPT
The PRODUCT FEEDS on this ChatGPT Ads account (id, name, countries, currencies, the count of ingested products eligible for ads, and campaign count). A product-feed campaign (mode product_feed) advertises products from one of these; update_openai_ads_feed_products fills one. Read-only, 0 credits.

list_openai_ads_lead_forms

ChatGPT
The ChatGPT Ads LEAD FORMS on this ad account — id, status (draft/published/archived), draft and published revision ids, fields. A lead form is what a Business Agent uses to collect a name/email/choice inside the chat; it only collects once PUBLISHED, and leads only arrive somewhere once a lead-sync subscription exists (list_openai_ads_lead_sync). Read-only, 0 credits. Needs ChatGPT Ads connected.

list_openai_ads_lead_sync

ChatGPT
The lead-sync subscription(s) on this ChatGPT Ads account — the webhook that receives every lead a lead form collects. With none, leads have nowhere to go. Read-only, 0 credits.

list_openai_ads_spend_windows

ChatGPT
Account-level SPENDING LIMITS on ChatGPT Ads, both kinds: the date-range SPEND-LIMIT WINDOWS (a ceiling on what the whole account may spend between an inclusive start date and an exclusive end date, with amount and spent so far) and the DAILY LIMIT (a per-day ceiling that renews at midnight in the account timezone, with what is spent and left today). Also returns the configuration revision that set_openai_ads_daily_spend_limit and remove_openai_ads_daily_spend_limit need as expectedRevision, and the earliest date a new daily limit may start. A limit caps spend; it never makes anything spend. OpenAI serves account spending limits only to ad accounts on postpaid INVOICE billing, and every spending-limit request, reads included, needs billing-management permission: a card-billed account is refused (a 403 that says only admins can do it) from any key, including one its own admin created. Read-only, 0 credits.

list_pinterest_ads_campaigns

ChatGPT
Read the brand’s connected Pinterest AD account(s). Call with NO adAccountId to list the ad accounts shared with this profile — do this first to pick a target. Call WITH adAccountId to list that account’s campaigns (id, name, status, objective, budget, and Pinterest’s own summary status). Add campaignId to get that ONE campaign’s whole tree — its AD GROUPS and ADS with their ids, statuses and review status. That is the only way to enumerate them, and it matters: Pinterest has no delete, so an ad group you cannot see is one you cannot even archive. All money is in the AD ACCOUNT’S currency, which is not necessarily dollars. Pinterest’s own list default hides DRAFT and ARCHIVED objects; this asks for all four statuses so nothing is silently missing. Read-only, free. Needs Pinterest connected (Settings > Connectors > Pinterest) and the ad account ticked under Manage accounts (or call list_connector_accounts with provider pinterest_ads, then set_connector_accounts).

list_pinterest_boards

ChatGPT
List the boards on the user’s connected Pinterest account — id, name, privacy and pin count. ALWAYS call this before post_to_pinterest and let the USER pick: Pinterest requires a board and Hermoso never chooses one for them. Read-only, 0 credits. Needs Pinterest connected (Settings > Connectors > Pinterest).

list_pinterest_pins

ChatGPT
The Pins on one of the account’s boards — or, with no boardId, the account’s own Pins across all of them. Each row carries the Pin id, title, description, destination link, alt text, board, creation date, and whether it HAS BEEN PROMOTED in an ad. THIS IS WHERE THE pinId EVERY OTHER PIN TOOL NEEDS COMES FROM: post_to_pinterest returns an id only at the instant it pins, so an agent that did not itself just pin had no way to name a Pin. Prefer passing a boardId — Pinterest’s own spec warns the account-wide listing has known timeouts. Read-only, 0 credits. Needs Pinterest connected.

list_playbooks

ChatGPT
List the PLAYBOOKS saved in this workspace — the reusable strategy cards (winning hooks, angles, formats and the concrete plays to run) kept from teardowns, angle mining and creatives worth repeating. The same Playbooks the web app's Playbooks tab lists. Read one before planning an ad so you re-run what already worked instead of starting cold. Read-only, free.

list_posthog_annotations

ChatGPT
The markers already on this PostHog project's charts — releases, experiments, incidents — with their timestamps, scopes and who wrote them. READ THESE BEFORE EXPLAINING A CHANGE IN A CHART: an annotation is an explanation somebody already wrote down. NEEDS THE annotation:read SCOPE on the personal API key, and a key created before Hermoso supported annotations will not have it — PostHog's own refusal NAMES the scope, and that is a scope to add in PostHog > Settings > Personal API keys, NOT a broken connection and NOT a reason to reconnect. Returns ONE page and hands back the next offset rather than paging for you, because PostHog's policy is that a third-party connector is not an exporter. Read-only, 0 credits.

list_posthog_projects

ChatGPT
The PostHog projects the connected personal API key can see, and which ONE this profile is pointed at. Only the ACTIVE project is readable. That is deliberate: PostHog's API otherwise falls back to "the last project you visited in the UI", which would make every answer depend on the user's browsing history, so Hermoso pins a project at connect time instead of relying on their implicit default. To move this profile to a different project, the user reconnects PostHog under Settings > Connectors > PostHog, or with connect_connector, with that project id. Read-only, 0 credits.

list_product_photos

ChatGPT
List the product photos ALREADY saved in your workspace — the brand's product library plus any app-store screens (also surfaces photos locked in your OTHER creations, since a set product lands in the shared library). FREE — returns each photo's url + label. Call it before set_product_image to see the existing photos you can reuse. Reads YOUR saved brand (pass brandId to target a specific profile — that switches this key's active profile like use_brand).

list_published_posts

ChatGPT
List every post Hermoso has recorded publishing for this brand, newest first, across all channels — channel, permalink, caption, format, the HOOK and SUBJECT it was written to, and its measured engagement. The WHOLE history, no cap: pass the reply's nextCursor as cursor for older posts. Each row says how it was recorded: 'captured' (written at publish time — the hook is what the author intended) or 'backfilled' (reconstructed from the platform afterwards). A dash for engagement means the platform reported no number — NOT zero. Read-only, 0 credits.

list_reddit_ads_assets

ChatGPT
The IMAGE and VIDEO assets stored on a Reddit profile, with status, dimensions and media urls; filter by type and name, paginate with pageToken. Reddit’s list is media-only: HEADLINE and CTA assets are stored but never listed here — read those by id with get_reddit_ads_asset (ids come from upload_reddit_ads_assets). OR pass postId (t3_…) to list the assets ONE POST uses, every type, each with its role (HEADLINE, IMAGE, VIDEO, CTA, THUMBNAIL, CAROUSEL_ITEM, HERO, SECONDARY_IMAGE) and image crop: those asset ids are what reddit_ads_report’s ASSET_ID breakdown reports on. Free.

list_reddit_ads_audiences

ChatGPT
List the CUSTOM AUDIENCES (uploaded customer lists) on a Reddit ad account, with each one’s match-size range and status. Reddit will not deliver to an audience under about 1,000 matched redditors, and the reply says which ones fall short — an audience that is too small silently reaches nobody rather than erroring. Use an id here as customAudienceIds in ad-group targeting to retarget it, or as excludedCustomAudienceIds to suppress existing customers from a prospecting campaign. Read-only, free.

list_reddit_ads_campaigns

ChatGPT
Read the brand’s connected Reddit AD account(s). Call with NO adAccountId to list the ad accounts shared with this profile — do this first to pick a target. Call WITH adAccountId to read that account’s whole tree at once: campaigns, ad groups and ads, each with its configured status and Reddit’s own effective status (the effective one is what says whether it could actually serve — PENDING_APPROVAL, CAMPAIGN_PAUSED, REJECTED and so on). Read-only, free. Needs Reddit Ads connected (Settings > Connectors > Reddit Ads) and the ad account ticked under Manage accounts (or call list_connector_accounts with provider reddit_ads, then set_connector_accounts).

list_reddit_ads_lead_forms

ChatGPT
Listing and reading forms KEEPS WORKING past the 2026-09-21 sunset — Reddit deliberately leaves GET/LIST up so advertisers can retain their records — so use this to EXPORT what exists before the deadline. Reddit exposes no API for the LEADS a form collected; those are downloaded from Ads Manager, and existing onsite-form ads are paused on 2026-09-30. List the lead generation forms on a Reddit ad account, with the fields each one asks for. Reddit publishes NO endpoint for reading the leads a form has collected — the user downloads those from Reddit’s Ads Manager. Say that plainly if asked for the leads themselves; do not imply they can be fetched. Read-only, free.

list_reddit_ads_pixels

ChatGPT
List the conversion pixels on a Reddit ad account, each with the LAST TIME IT FIRED — which is the difference between "a pixel exists" and "conversion tracking works". Call this before building anything: since 13 July 2026 Reddit REQUIRES a pixel on every ad group and every CBO campaign, so an account with none cannot run ads at all. IMPORTANT: the Reddit API has no operation that creates a pixel — if the account has none, the only fix is for the user to add it in Reddit’s Events Manager (ads.reddit.com > Events Manager); never claim you can create one. Read-only, free.

list_reddit_ads_posts

ChatGPT
List the POSTS on a Reddit profile — these are the creatives Reddit ads promote. Use it to find an existing post to advertise rather than creating a near-duplicate. Call list_reddit_ads_profiles first for redditProfileId. Read-only, free.

list_reddit_ads_profiles

ChatGPT
List the Reddit PROFILES attached to an ad account. A Reddit ad promotes a POST, and every post is published AS one of these profiles — so this is the first call in any Reddit creative build, and its id is what create_reddit_ads_post needs. If it comes back empty, the ad account has no profile attached and nothing can be advertised from it yet. Read-only, free.

list_reddit_ads_saved_audiences

ChatGPT
List the SAVED AUDIENCES on a Reddit ad account — named, reusable targeting definitions (communities, interests, geos, devices and so on) that an ad group can point at instead of repeating the whole block. The reply says how many live ad groups each one is attached to, which is what makes editing one a decision rather than a formality. Read-only, free.

list_scheduled

ChatGPT
Use this when the user asks what is scheduled, what already posted, or whether a post went out. Show what is queued to post and what already went out. Each fired item reports PER-CHANNEL outcomes, so you can see that (say) Instagram published and TikTok failed on the same item rather than a single misleading verdict. Past items are rebuilt from published-post records, one row per channel; they are not job runs (use list_jobs for those). THE LIST IS COMPACT so it fits in one reply: the next 25 queued and the last 15 fired, captions shortened. Pass id for ONE post in full (every caption and setting, which you need before reschedule_post replaces a caption map), channel to filter, or upcoming / fired for more rows. Read-only, 0 credits.

list_search_console_sitemaps

ChatGPT
The sitemaps registered on a Search Console property: when each was last submitted and last downloaded by Google, whether Google has processed it yet, its error and warning counts, and per content type how many URLs it SUBMITTED against how many are INDEXED. That last pair is the fastest read on whether a site has an indexing problem rather than a ranking one. Pass feedpath for one sitemap instead of the list, or sitemapIndex to list the sitemaps inside an index file. An empty list is a real answer — the property has no sitemap registered — and is not a failure. Read-only, 0 credits.

list_search_console_sites

ChatGPT
The Google Search Console properties SHARED WITH THIS PROFILE, each with the exact property string every other Search Console tool takes and the connected account's permission level on it. CALL THIS FIRST. A property is EITHER "sc-domain:example.com" (a Domain property, covering every scheme and subdomain) OR the full URL-prefix form "https://example.com/" including scheme and trailing slash — Google treats those as different properties and one of them will 403, so resolve it here rather than guessing, and never pass a bare domain. THIS IS NOT EVERY PROPERTY THE GOOGLE ACCOUNT CAN SEE, deliberately: one login commonly holds a dozen clients' properties, so the user ticks which belong to THIS profile and any other one is refused by name. An empty list means nothing is ticked yet — say so and point the user at Settings > Connectors > Google Search Console > Manage accounts (or call list_connector_accounts / set_connector_accounts with provider "google_search_console"); never name or guess a property. A row whose permissionLevel is siteUnverifiedUser will refuse every later call. Read-only, 0 credits. Needs Google Search Console connected.

list_sheet_tabs

ChatGPT
The tabs in a Google Spreadsheet, each with its name, numeric sheetId, row/column count and position. Call this BEFORE naming a tab in update_sheet / clear_sheet_range / manage_sheet_tabs / format_sheet, and before proposing to delete one — it is how you learn what the file actually contains instead of guessing at a name. Read-only, free.

list_shopify_products

ChatGPT
The merchant's real Shopify products — id, title, description, price, images and storefront URL. This is where the productId for publish_to_shopify_product comes from, and it doubles as ground truth about what the brand actually sells (real titles and real photos, not a guess from the website). Newest-updated first. Only works for accounts created by installing Hermoso from the Shopify App Store. Read-only, free.

list_skills

ChatGPT
List the bundled Hermoso SKILLS — multi-step workflow instructions (SKILL.md) that orchestrate the other tools (research an ad space, plan+render a finished ad, product photoshoot, raw generation) — plus the in-app strategy skills and creative recipes. Call get_skill to load a bundle. Read-only, free.

list_snapchat_ads_accounts

ChatGPT
List the Snapchat AD ACCOUNTS SHARED WITH THIS PROFILE — the ones it may actually build on and spend from, which is NOT everything the Snapchat login can reach — id, name, currency, timezone and status. Every other Snapchat Ads tool needs an adAccountId and this is where it comes from. One call returns both tiers, because Snap nests ad accounts inside their organization. An account flagged as a TEST account is marked as such — those cannot serve real ads. Read-only, free.

list_snapchat_ads_campaigns

ChatGPT
Read the whole Snapchat ad tree for an ad account — campaigns, ad squads and ads with their statuses. The three tiers are fetched separately so one failure cannot take the tree down, and a tier that FAILED to read is reported in partial rather than as an empty list: an empty list here means an empty account, never a failed read. Read-only, free.

list_snapchat_ads_profiles

ChatGPT
FIND THE PUBLIC PROFILE ID EVERY SNAPCHAT AD CREATIVE REQUIRES. Snapchat has required profile_properties on every creative since 2024-02-26, so without one upload_snapchat_ads_creative cannot build anything — this is where the id comes from. Snapchat publishes no clean list-profiles endpoint, so this reads the ad account SHARING POLICIES (the documented mechanism by which a profile reaches an ad account) and reports which resource-type token Snapchat accepted. IT CAN LEGITIMATELY FAIL: the Public Profile API is on a different host and documents its own OAuth scope which this connection does not hold, and a failure is reported as "could not tell" with the manual way out — NEVER as "you have no profiles", because an empty list there would look like a real answer. Read-only, free.

list_stripe_charges

ChatGPT
Recent charges on the brand's OWN Stripe account — amount, currency, status, refunds, customer email, description — newest first, optionally for one customer. Read-only, free. Needs Stripe connected.

list_stripe_coupons

ChatGPT
Coupons on the brand's Stripe account: percent or amount off, duration, validity and redemptions. Read-only, free. Needs Stripe connected (Settings > Connectors > Stripe).

list_stripe_customers

ChatGPT
Recent customers on the brand's OWN Stripe account — email, name, created, currency, delinquent — newest first, or one customer by exact email. Read-only, free. Needs Stripe connected.

list_stripe_products

ChatGPT
Products on the brand's Stripe account with their active prices (amount, currency, one-time or recurring interval), which is what create_stripe_payment_link and create_stripe_subscription take. active:false lists archived products too. Read-only, free. Needs Stripe connected (Settings > Connectors > Stripe).

list_stripe_refunds

ChatGPT
Refunds on the brand's Stripe account, newest first: amount, status, reason and the charge refunded; filter by charge. Read-only, free. Needs Stripe connected (Settings > Connectors > Stripe).

list_stripe_subscriptions

ChatGPT
Subscriptions on the brand's OWN Stripe account with each item's price in MAJOR units (19.00 means $19.00), interval and quantity, and the MRR each contributes. With NO status it returns everything that counts toward MRR — active + trialing + past_due — and says so; pass status all, canceled, unpaid, incomplete, incomplete_expired or paused for the rest, or filter by customer or price. Read-only, free. Needs Stripe connected (Settings > Connectors > Stripe).

list_swipefile

ChatGPT
List this workspace's SWIPEFILE — the saved-ad research board: every named collection and the ads/creatives kept in it (advertiser, headline, body copy, media URL, platform, when it was saved, and any taste tags). The SAME board the web app's Swipefile tab shows. Use it to answer "what have we saved?", to mine the user's own taste before planning an ad, or to find a reference to remix. Read-only, free.

list_tag_manager_containers

ChatGPT
The Google Tag Manager containers SHARED WITH THIS PROFILE, with the exact containerPath every other Tag Manager tool needs. CALL THIS FIRST. A container is addressed by PATH, "accounts/<id>/containers/<id>", never by the GTM-XXXXXX public id a human reads off the snippet: no Tag Manager method accepts the public id, so passing one is refused. Each row carries the public id beside the path so you can match what the user says to what the API wants. THIS IS NOT EVERY CONTAINER THE GOOGLE ACCOUNT CAN SEE: one agency login routinely administers every client's container, and a container is not data about a client, it is control of what runs on their website, so the user ticks which containers belong to THIS profile and only those are reachable. An empty list means nothing is ticked yet: say so and point the user at Settings, Connectors, Google Tag Manager, Manage accounts (or call list_connector_accounts with provider google_tag_manager, then set_connector_accounts). Never name or guess a container. Read-only, free. Read-only, 0 credits. Needs Google Tag Manager connected.

list_tag_manager_tags

ChatGPT
WHAT IS ACTUALLY FIRING ON THE SITE. Every tag in the container's workspace with its type, whether it is paused, which triggers fire it by name, and the one field that matters most: whether each tag is LIVE on the website or only STAGED in a draft. Also returns the container's triggers so you can reuse one instead of creating a duplicate. USE THIS TO ANSWER "is conversion tracking actually working": a Google Ads conversion action created by create_google_ads_conversion_action records nothing until a tag in this container fires it, and this is the only way to see whether that tag exists, whether it is paused, and whether it has been published. THREE VALUES FOR live, AND THEY ARE DIFFERENT ANSWERS: true means it is in the published container version and running, false means it is staged and NOT running yet, and null means the published version could not be read so you cannot tell. Never report null as false. If the response says the container has never been published, nothing in it is running at all. Read-only, free. 0 credits. Needs Google Tag Manager connected.

list_tag_manager_versions

ChatGPT
THE CONTAINER'S VERSION HISTORY, and the thing to read before proposing to publish or to roll one back. Every version with its id, its name, how many tags, triggers and variables it holds, whether it is archived, and which one is LIVE on the site right now. This is what makes a publish reversible: pass a row's path as publish_tag_manager_container's versionPath to put that version back on the site. The live flag is three-valued — true, false, or null when Google would not tell us which version is live, and null must never be read as false. Read-only. 0 credits. Needs Google Tag Manager connected.

list_team

ChatGPT
List the members of the current profile — email, role (admin/member) and status. Read-only, free.

list_telegram_chats

ChatGPT
Find the chat ids this Telegram bot can be addressed by. STATE THE LIMIT WHENEVER YOU USE IT: this is NOT the list of chats the bot belongs to — the Bot API publishes no such method — it is every chat that SENT the bot an update in the last 24 hours, which is as long as Telegram keeps an update. A channel the bot posts to every day but nobody messages will NOT appear here, and its absence means nothing at all: post to it by @username or numeric id anyway. If the bot has an outgoing WEBHOOK configured the list is empty for that reason alone (Telegram: getUpdates "will not work if an outgoing webhook is set up"), and the reply says so rather than reading as an empty account. Nothing is consumed — no update offset is confirmed, so this cannot eat the bot’s pending updates. Free, 0 credits.

list_telegram_dms

ChatGPT
Read what people have SENT to the connected Telegram bot — the newest message from each chat, newest chat first, so you can see who is waiting on a reply. Reply with post_to_telegram using the chatId shown. Warning: THIS IS A ROLLING 24-HOUR WINDOW, NOT AN INBOX. Telegram keeps undelivered updates for 24 hours and publishes NO history endpoint at all, so anything older is unrecoverable — never report an empty result as 'you have no messages', report it as 'nothing in the last 24 hours'. Two more Telegram rules worth stating before someone concludes the feature is broken: a bot that has an outgoing WEBHOOK configured gets nothing from this at all (Telegram's own rule, and the reply says so), and a bot can never message someone first — they have to write to it. Free — no vendor charge and no credits. Needs Telegram connected (Settings > Connectors > Telegram).

list_threads_mentions

ChatGPT
Posts where someone MENTIONED the brand on Threads — anywhere, not just under your own posts. This is brand listening: real objections, questions and the exact language customers use, which is strong raw material for ad copy and for mine_angles. Use list_threads_replies instead when you want the conversation under one specific post.

list_threads_posts

ChatGPT
List recent posts on the brand’s connected Threads account (id, text, media, permalink, timestamp). Use it to find a post id for threads_insights, list_threads_replies, reply_to_thread or delete_thread.

list_threads_profile_posts

ChatGPT
The recent PUBLIC posts of any Threads account — the raw material for a teardown, for mine_angles, and for ad copy in the language the market actually uses. Use it after threads_profile, or on its own when you already know the handle, and then USE what it returns: draft the brand’s next post or plan_ad off the angles you found. AN EMPTY LIST IS NEVER PROOF THEY HAVE NOT POSTED — Meta returns nothing for a private account, for any account under 100 followers, and when the Threads connection predates the threads_profile_discovery permission (approved for the app 2026-09-05; reconnect Threads to grant it); the note says which of those applies. Read-only, 0 credits. Needs Threads connected.

list_threads_replies

ChatGPT
Read the replies on a Threads post. Set conversation:true to walk the entire thread rather than only direct replies. Use before reply_to_thread so you answer with the actual conversation in view.

list_tiktok_ads_accounts

ChatGPT
List the TikTok ADVERTISER accounts this profile can act on — id, name, currency, timezone and status. Every other TikTok Ads tool needs an advertiserId, and this is where it comes from: call this first and let the USER pick when there is more than one. Read-only, free, spends nothing. Needs TikTok ADS connected (Settings > Connectors > TikTok Ads) — that is a DIFFERENT connection from the TikTok posting connector behind post_to_tiktok, so a brand that publishes to TikTok every day may still have nothing here.

list_tiktok_ads_app_events

ChatGPT
The conversion events an APP campaign may actually optimise toward on one app — the app twin of list_tiktok_ads_pixels, and the same distinction between "an event exists" and "an ad group can optimise for it". An event reporting INSUFFICIENT_POSTBACK is NOT broken and NOT misconfigured: TikTok needs more measured conversions before it will optimise toward it, and unlockThreshold says how many against conversions in the last 30 days. Only an event with usableForOptimization:true can be used today. A brand-new app with no measured conversions legitimately returns none. Read-only, free.

list_tiktok_ads_apps

ChatGPT
The mobile apps registered on a TikTok ad account. An APP_INSTALL campaign cannot be built without one, and an APP-activity audience uses these ids as its event sources — so this is the missing lookup that made APP_INSTALL offerable and undeliverable in the same product, exactly as conversion campaigns were before pixels. Registering a NEW app is deliberately not offered here: it needs the store listing and the attribution provider, and it belongs in TikTok Ads Manager rather than being set on a user’s behalf. Read-only, free.

list_tiktok_ads_audiences

ChatGPT
The custom audiences on a TikTok ad account — retargeting pools built from real behaviour (website pixel, app activity, ad engagement, organic posts, leads, shop activity) plus any lookalike grown from them. Until this existed, TikTok targeting through Hermoso was interests-and-geo only. THE FIELD THAT DECIDES EVERYTHING IS usableInAdGroups: TikTok takes up to 48 HOURS to process a new audience, so isValid:false immediately after creation is the normal processing window rather than a failure, and only a valid audience changes delivery. ownership tells you whether this advertiser owns the audience or another one shared it. detail:true with audienceIds reads /dmp/custom_audience/get/ instead — adding the rule, the auto-refresh setting and any error message TikTok recorded. Read-only, free.

list_tiktok_ads_blocked_words

ChatGPT
The ad account’s blocked-word list — a standing filter that hides ANY comment containing one of these words, automatically, across every ad on the account, before a human ever sees it. NOTHING ELSE IN HERMOSO DOES THIS: Meta and YouTube moderation here are both per-comment and after the fact, so this is the only place a brand can decide once that a slur or a competitor’s name never appears under its ads again. TikTok caps the list at 500 words per ad account and the reply says how many slots are left. Pass check with words to ask TikTok directly whether each one is already blocked — that answer is authoritative across the whole list rather than across the page you happened to fetch. Read-only, free.

list_tiktok_ads_business_centers

ChatGPT
The TikTok Business Centers reachable on this connection. THIS IS THE ONE PIECE OF STRUCTURE THAT MAKES THE CATALOG TOOLS DIFFERENT FROM EVERY OTHER TIKTOK TOOL: a product catalog belongs to a Business Center, not to an ad account, so the catalog tools take a bcId from here and an advertiserId will not work. With exactly one Business Center reachable the catalog tools resolve it themselves; with several they refuse and name them rather than guessing, because a catalog created under the wrong Business Center is invisible to the ad account that needed it and nothing in the reply would say so. Read-only, free.

list_tiktok_ads_campaigns

ChatGPT
Read a TikTok advertiser account’s whole tree in one call — campaigns, ad groups and ads, each with the operation status that says whether it is enabled at all. Omit advertiserId when the brand reaches exactly one account; with several, the call names the choices rather than picking for you. THE THREE TIERS ARE READ SEPARATELY AND A TIER THAT FAILED IS REPORTED AS FAILED, never as empty — if the result carries a partial note, say which tier could not be read instead of telling the user they have no ads. Read-only, free. TikTok’s QPS is 1, so a big account reads back slowly: that is the throttle working, not a fault.

list_tiktok_ads_catalog_products

ChatGPT
The products inside a TikTok catalog, each with whether it can ACTUALLY be advertised and — when it cannot — TikTok’s own rejection reason together with its suggested fix and which placements and countries were affected. usableInAds is true only when all three of audit approved, active status ACTIVATED and availability IN_STOCK / AVAILABLE_FOR_ORDER / PREORDER hold, so a catalog full of "approved" products can still be entirely unadvertisable and the raw audit status alone would not say so. Narrow with productIds, skuIds or productSetIds; productIds and skuIds cannot be used together (TikTok’s own rule), and TikTok returns at most 10,000 rows in total so page x pageSize is capped rather than paged into a 400. Read-only, free.

list_tiktok_ads_catalog_sets

ChatGPT
The product SETS in a TikTok catalog — the slices an ad group can target instead of the whole catalog — each with its filter conditions and how many products it holds. Product sets are built in TikTok Ads Manager rather than here, and this reads what exists. On a very large set pass productCount:false, because counting products can time the call out at TikTok’s end — that is TikTok’s own caveat, not a guess. A null productCount means it was not requested or not returned, not that the set is empty. Read-only, free.

list_tiktok_ads_catalogs

ChatGPT
The product catalogs in a TikTok Business Center — the containers Dynamic Product Ads sell from — each with HOW MANY OF ITS PRODUCTS ARE APPROVED, REJECTED OR STILL PROCESSING, which is the difference between "the feed ran" and "the feed worked". A products:null on a row means those counts COULD NOT BE READ for that catalog, which is not the same as a catalog with no products. Pass regions:true to also get the regions a new catalog may target — worth doing BEFORE creating one, because a catalog’s region and currency are set once and can never be changed. Read-only, free.

list_tiktok_ads_comments

ChatGPT
The public comments people have left on your TikTok ads — the read that finally makes TikTok comment moderation possible through Hermoso, where Meta and YouTube moderation already shipped. Covers both paid-impression ads and Spark Ads. TWO THINGS ARE NOT NEGOTIABLE AND BOTH COME FROM TIKTOK: comments are scoped to an AD GROUP and to nothing else (their search_field accepts the single value ADGROUP_ID, so there is no way to ask for one ad or a whole campaign — get ad group ids from list_tiktok_ads_campaigns), and startTime->endTime may span AT MOST 30 DAYS. That 30-day cap is not in TikTok’s parameter table; it was measured, and asking for more answers an opaque "The maximum allowed time span is 30 days" — so an empty result means "none in these 30 days", never "none ever", and covering a longer period means one call per slice. Every row carries what the other tools need: commentId, adId, tiktokItemId, identityId and identityType, plus hitBlockedWord (caught by your blocked-word list), isPinned, likes, replies, and canDelete — TikTok’s own per-comment answer to whether YOU may delete it, which is false for anything a member of the public wrote. Read-only, free.

list_tiktok_ads_crm_event_sets

ChatGPT
The CRM Event sets on a TikTok ad account. A CRM Event Set is where LEAD-LIFECYCLE events go — a lead that became qualified, booked a demo, or closed — and sending those back with send_tiktok_ads_events (eventSource "crm", eventSourceId = the set’s id) is what lets TikTok optimise a LEAD_GENERATION campaign toward leads that actually convert rather than toward form fills. An advertiser is capped at fifty sets. Read-only, free.

list_tiktok_ads_custom_conversions

ChatGPT
List the Custom Conversions defined on a TikTok EVENT SOURCE — narrower, rule-based conversions built on top of a pixel event (for example "Purchase, but only on /checkout/premium"). A Custom Conversion belongs to an event source, NOT to an advertiser, so TikTok requires eventSourceType and eventSourceId; with none given this resolves the single pixel on the advertiser when there is exactly one and otherwise asks which. Pass a Custom Conversion to create_tiktok_ads_ad_group as customConversionId to optimise toward the narrow rule instead of the broad standard event. TikTok accepts it ONLY alongside a pixel and only when optimizationGoal is CONVERT or IN_APP_EVENT, and only when its own optimizationEvent matches the one on the ad group; usableForAds reports whether its activity status allows it. Read-only, free.

list_tiktok_ads_identities

ChatGPT
List the IDENTITIES on a TikTok advertiser account — the TikTok accounts an ad is allowed to appear as. THIS IS A HARD PREREQUISITE, not a convenience: a TikTok ad carries no identity of its own and there is NO DEFAULT, so create_tiktok_ads_ad refuses without an id from here. Call it, show the user the list, and let them CHOOSE — an ad runs publicly under whichever account is named, so picking one for them is a public mistake on somebody else’s profile. If it comes back empty, no TikTok account has been authorised on this advertiser yet and nothing can be advertised from it; say that rather than guessing an id. Read-only, free.

list_tiktok_ads_identity_posts

ChatGPT
List the real ORGANIC TikTok posts published by one identity — the account itself, not the ad library. THIS IS WHERE A SPARK AD STARTS: pass an itemId from here to create_tiktok_ads_ad as tiktokItemId and the ad IS that post, with its own comments, likes and shares, running under the account that made it. Use this for an account the brand controls or has been granted (identityType TT_USER or BC_AUTH_TT); for a CREATOR’s post they authorised with a code, use list_tiktok_ads_spark_posts instead. identityId and identityType both come from list_tiktok_ads_identities, and a BC_AUTH_TT identity ALSO needs the identityAuthorizedBcId reported next to it — TikTok refuses the call without it. TikTok defaults to VIDEO posts only, so pass itemType:"CAROUSEL" to see photo posts; an account that posts only photos otherwise looks empty. Paginated by CURSOR, not page: pass back the cursor while hasMore is true. posterUrl and previewUrl EXPIRE AFTER AN HOUR — show them, never store them. Read-only, free.

list_tiktok_ads_lead_fields

ChatGPT
The questions a TikTok Instant Form asks — which is exactly the set of columns download_tiktok_ads_leads will return. It is NOT a fixed schema: it is whatever that form was built to ask, so read it before promising a user which fields their leads carry. leadSource DIRECT_MESSAGE reads the fields collected in the associated Business Account’s direct messages instead, and takes no pageId (TikTok says it is "not supported" there). Read-only, free.

list_tiktok_ads_lead_forms

ChatGPT
The Instant Forms (lead forms) on a TikTok ad account — this is where the pageId every other lead tool needs comes from. A form whose status is EDITED is a DRAFT and has never been able to collect anything, which is a different thing from "a form with no leads". THERE IS NO API THAT CREATES AN INSTANT FORM: they are built in TikTok Ads Manager, so an advertiser with none needs one made there (or a LEAD_GENERATION campaign using promotionTargetType EXTERNAL_WEBSITE, which collects on your own site and has no form here). Read-only, free.

list_tiktok_ads_offline_event_sets

ChatGPT
The Offline Event sets on a TikTok ad account — the containers real-world conversions are reported into: an in-store purchase, a phone booking, a signed contract, a call-centre sale. An id here is what send_tiktok_ads_offline_events reports into, and what send_tiktok_ads_events wants as eventSourceId when eventSource is "offline". The reply also counts how many are AUTO-TRACKING, which matters because an auto-tracking set is attached to every campaign created afterwards and TikTok caps an advertiser at ten of them. Read-only, free.

list_tiktok_ads_payment_portfolios

ChatGPT
The payment portfolios under a TikTok Business Center; how a customer’s ad accounts are actually funded, whether the money is pooled across them (an Advanced portfolio) or held per account (a Standard one), how many accounts draw on each, and what credit line it carries. WORTH READING WHEN CAMPAIGNS STOP DELIVERING, because that is very often a funding answer rather than an ads answer, and nothing else in Hermoso can see it. By TikTok’s own default this lists every portfolio belonging to the same CLIENT as the Business Center, including ones not linked to it; pass bcRelated:true to narrow it. READ-ONLY BY DESIGN: Hermoso never creates a portfolio, moves an ad account onto one, or allocates a credit line, because each of those decides where a customer’s money sits. Free.

list_tiktok_ads_pixels

ChatGPT
List the conversion pixels on a TikTok ad account, each with the EVENTS it can optimise toward — which is the difference between "a pixel exists" and "this campaign can actually optimise". Call it before building any conversion campaign (WEB_CONVERSIONS, CONVERSIONS, PRODUCT_SALES, LEAD_GENERATION): those need optimizationGoal CONVERT, which TikTok refuses without a pixel. Only an event reported under optimizationEvent can be used — TikTok returns none for PAGE_VIEW because it "cannot be used for optimization". An EMPTY event list is not proof a pixel is idle: TikTok refreshes pixel event data every 2-4 hours, so a pixel that started firing recently still reports []. Read-only, free.

list_tiktok_ads_rules

ChatGPT
THE AUTOMATED RULES RUNNING ON A TIKTOK AD ACCOUNT, and — the part that matters — which of them can move money. Every row carries canSpend, the specific actions that put it there (armingActions), whether its reach is UNBOUNDED (a rule targeting "all active campaigns" re-resolves that set at every run, so it covers campaigns created after it was written), and a scopeToken. That token is what set_tiktok_ads_rule_status wants echoed back before it will arm a spending rule, and it is computed from the rule AS TIKTOK STORES IT — so it also detects a rule that has been edited in TikTok Ads Manager since it was read. The reply leads with the rules that are armed AND able to spend, because "what is running on my account that I am not watching" is the actual question. Pass ruleIds for specific rules, or filter by status / dimension / name. Read-only, free.

list_tiktok_ads_spark_posts

ChatGPT
List the TikTok posts a post OWNER has authorised this ad account to promote — the creator-collaboration half of Spark Ads. These are posts from accounts the brand does NOT control: the owner generates an authorization code in the TikTok app and authorize_tiktok_ads_spark_post applies it, after which the post appears here with the itemId create_tiktok_ads_ad needs as tiktokItemId. AN EMPTY LIST IS A NORMAL STARTING STATE, not a failure — it means nobody has authorised a post yet, and the fix is to ask a creator for a code, not to retry. Each row carries the authorization WINDOW (authStartTime / authEndTime): a Spark Ad cannot outlive its authorization, so check the end time before building a campaign around one. Read-only, free.

list_tiktok_ads_store_products

ChatGPT
The products inside a TikTok Shop, with titles, images, price ranges, currency and historical sales. Pass adCreationEligible to also learn which of them may ACTUALLY be advertised; CUSTOM_SHOP_ADS for Shopping Ads, GMV_MAX for a Product GMV Max campaign; and note that WITHOUT it TikTok does not report eligibility at all, so an absent flag means unknown rather than yes. That filter additionally requires an advertiserId, because eligibility is a fact about a product AND an ad account rather than about a product alone. This call is keyed on a BUSINESS CENTER, which list_tiktok_ads_stores reports on each store row. Read-only, free.

list_tiktok_ads_stores

ChatGPT
The TikTok Shops granted to a TikTok ad account; what Shopping Ads and GMV Max campaigns sell from. Each row also names the BUSINESS CENTER that can reach the shop, and that is what list_tiktok_ads_store_products needs, because the product endpoint is keyed on a Business Center rather than on this ad account. An empty result is a real answer rather than an error: a shop has to be created in TikTok Shop Seller Center and granted to the ad account, and this reads that grant rather than creating one. Read-only, free.

list_tiktok_ads_verification_documents

ChatGPT
Which identity or business documents TikTok accepts for verification in a given country; the list differs everywhere, so there is no useful default and the country is required. Returns each document’s name and the code submit_tiktok_ads_verification needs. business_type BUSINESS asks about company paperwork (an EIN letter, a certificate of incorporation); INDIVIDUAL asks about personal identification (passport, driver’s licence, residence card). Read-only, free.

list_tiktok_brand_hashtag_posts

ChatGPT
Public posts whose captions carry one of the brand hashtags this account has enabled. It is the hashtag half of brand monitoring, where list_tiktok_mentions covers @-mentions. Omit hashtag for the top posts across every enabled tag; pass one to narrow to it. TWO TIKTOK BEHAVIOURS THAT READ AS BUGS IF NOBODY SAYS THEM: the hashtag filter is CASE-SENSITIVE and must exactly match an enabled tag, and filtering to one tag makes matched_hashtags come back EMPTY on every row. NOTHING IS RETURNED UNTIL HASHTAGS ARE ENABLED. That is a setup step rather than a result: use list_tiktok_brand_hashtags and enable_tiktok_brand_hashtags / disable_tiktok_brand_hashtag first, and allow 24 hours after enabling. Capped at the top 1,000 posts of the last 90 days. NEEDS THE TIKTOK ACCOUNT AUTHORIZATION with the brand-insights permission. Read-only, free.

list_tiktok_brand_hashtags

ChatGPT
The brand hashtags this account curates, in two flavours. kind:'ENABLED' is what TikTok is currently counting (with the date each was turned on, and whether it is old enough to remove). kind:'AVAILABLE' is what TikTok will ACCEPT. A tag qualifies once it has appeared in at least three post captions, either from this account or from a post mentioning its handle. A tag containing the account handle as a substring can also be enabled even when it is not on the available list. AT MOST 50 CAN BE ENABLED PER BRAND. TikTok's own caveat on the available list: test and newly created accounts often return nothing and should not be used to judge whether this works. NEEDS THE TIKTOK ACCOUNT AUTHORIZATION with the brand-insights permission. Read-only, free.

list_tiktok_comment_replies

ChatGPT
All replies to a single comment on a post the authorized TikTok account owns — the complete list, where list_tiktok_comments only ever attaches three. TIKTOK DOES NOT RETURN REPLIES TO A HIDDEN COMMENT AT ALL, so an empty list against a hidden parent means 'cannot read', never 'no replies' — check the parent's status first if the answer looks wrong. NEEDS THE TIKTOK ACCOUNT AUTHORIZATION (see tiktok_account_status). Read-only, free.

list_tiktok_comments

ChatGPT
Read the comments on a TikTok post the AUTHORIZED ACCOUNT OWNS — this is TikTok's answer to list_meta_comments and list_youtube_comments. It sees BOTH public and hidden comments, and by default returns both: TikTok's status defaults to ALL, so the list mixes comments the owner hid with comments TikTok's own moderation, privacy or spam filters hid, and those are not the same thing (the second kind may refuse to unhide). Pass status:'PUBLIC' for only what the public sees. A row carrying parentCommentId IS A REPLY, not a top-level comment — that is how TikTok distinguishes them. include_replies attaches at most THREE replies per comment; use list_tiktok_comment_replies for all of them. NEEDS THE TIKTOK ACCOUNT AUTHORIZATION, which is a separate consent from the TikTok Ads advertiser connection — tiktok_account_status says whether this profile has it. Read-only, free.

list_tiktok_creator_labels

ChatGPT
TikTok’s creator category labels, in two sets that are NOT interchangeable. labelType "SEARCH" gives the tags discover_tiktok_creators filters on: contentLabelIds is what a creator POSTS about, industryLabelIds the commercial categories they have actually worked in. labelType "RANKING" gives the labels tiktok_creator_leaderboard needs, and there the pairing is strict: BRANDED_CONTENT ranks by an INDUSTRY label, ORGANIC_CONTENT by a CONTENT label. Both arrive as bare numeric ids that look identical, and TikTok reports a mismatch as an invalid field rather than as the wrong kind of label, so read this first rather than guessing. Read-only, free.

list_tiktok_mention_comments

ChatGPT
Comments and replies anywhere on TikTok whose TEXT @-mentions the connected account's handle. This is the conversational half of brand monitoring, where list_tiktok_mentions covers post captions. Same four preconditions as that tool (public post, 90 days, adult author, BUSINESS account), so an empty list is not the same as silence. Capped at the top 1,000 by comment likes, and TIKTOK DELIBERATELY DEPRIORITISES a comment that is nothing but the @-mention with no other text, so bare tags may not appear at all. Sorts on different keys from the post list: VIDEO_LIKES, COMMENT_CREATE_TIME or COMMENT_LIKES. NEEDS THE TIKTOK ACCOUNT AUTHORIZATION with the brand-insights and comment-list permissions. Read-only, free.

list_tiktok_mentions

ChatGPT
Who is talking about the brand on TikTok: every public post whose CAPTION @-mentions the connected account's handle. This is TikTok's answer to x_mentions and list_threads_mentions, and it is the brand-monitoring read the product had for every other channel and not for TikTok. FOUR THINGS TIKTOK ENFORCES THAT MAKE AN EMPTY RESULT AMBIGUOUS, so read them before reporting silence: the mentioning post must be PUBLIC, it must be under 90 days old, its author must not be underage or of unknown age, and THE WHOLE FAMILY ONLY WORKS FOR A TIKTOK BUSINESS ACCOUNT, so a personal account returns nothing at all (tiktok_account_insights reports which one this is). Capped at the top 1,000 mentioning posts however many exist. VIEWS AND REACH COME BACK NULL, NOT ZERO, on any post under 1,000 of either. That is TikTok withholding a number, never a measurement of nothing. Thumbnail URLs expire after 48 hours. NEEDS THE TIKTOK ACCOUNT AUTHORIZATION with the brand-insights permission (tiktok_account_status says whether this profile has it). Read-only, free.

list_tiktok_smart_campaigns

ChatGPT
Read the Smart+ campaigns on a TikTok advertiser. TikTok’s reads LAG its writes, so a campaign created seconds ago can be missing here and still exist — the reply says so rather than reporting an empty account.

list_tiktok_tto_accounts

ChatGPT
The TikTok One accounts this TikTok connection can act on. TikTok One is TikTok’s influencer marketplace: find creators, invite them to a campaign, get their videos tagged to it, read organic-versus-paid performance on those videos, and ask them for Spark Ads authorization so the brand can put money behind their post. THIS IS THE ONE PIECE OF STRUCTURE THAT MAKES THE CREATOR TOOLS DIFFERENT: a TikTok One account id is a THIRD id space beside an advertiser id and a Business Center id, and every creator-marketplace tool needs one from here. With exactly one reachable the other tools resolve it themselves; with several they refuse and name them. It rides the SAME connection and the SAME OAuth flow as TikTok Ads and there is nothing extra to apply for; but a connection authorized before 2026-08-19 does not carry the TikTok One permission and its owner has to reconnect. Read-only, free.

list_tiktok_tto_anchors

ChatGPT
Use this when you need the webpage anchors (links above a creator’s video description) on the TikTok One account, with their status. Read-only, free.

list_tiktok_tto_brand_profiles

ChatGPT
The Brand Profiles on a TikTok One account. A Brand Profile is what a creator sees when they open an invite link; name, industry, logo, website and optionally the brand’s own TikTok account; and a brand-level campaign cannot exist without one. Read-only, free.

list_tiktok_tto_campaigns

ChatGPT
The TikTok One creator campaigns on this account, each with its invite link, the creators invited, the videos linked so far, the ad accounts that automatically receive Spark Ads rights, and the COUNTRY CODES that tiktok_tto_campaign_report needs. TWO TIKTOK BEHAVIOURS WORTH KNOWING: it pages five at a time, which is its own maximum rather than ours, and it defaults to campaignType CAMPAIGN: so a brand-level campaign is invisible here unless you ask for BRAND_LINK explicitly. Read-only, free.

list_tiktok_videos

ChatGPT
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.

list_vercel_deployments

ChatGPT
Deployments on the brand's Vercel account, newest first: URL, state (READY, BUILDING, QUEUED, ERROR, CANCELED...), target (production or preview), when it was created and became ready, the commit message and Vercel's inspector link. Filter by projectId, target or state, or pass deploymentId (an id or a deployment URL) to read one, e.g. to see whether a page you just deployed is READY. Read-only, free. Needs Vercel connected (Settings > Connectors > Vercel).

list_vercel_domains

ChatGPT
The domains on the brand's Vercel account (verified or not, when they expire), or with projectId the domains assigned to one project (and any redirect or git branch on them). Read-only, free. Needs Vercel connected (Settings > Connectors > Vercel).

list_vercel_projects

ChatGPT
The brand's Vercel projects (its websites): name, id, framework, production URL and state, the latest deployment (URL, state, target, when), the linked git repo, and whether Web Analytics and Speed Insights are on. search filters by project name. Read-only, free. Needs Vercel connected (Settings > Connectors > Vercel).

list_watch_findings

ChatGPT
Read what the standing COMPETITOR WATCH has found — the new ads each watched brand has launched since the last check, plus the watch's own state (who is watched, when it last ran, when it runs next, and whether the last run actually succeeded). The same board the web app's Ad Spy > Watching tab renders. Use it to answer "what are our competitors running that's new?", to feed a teardown, or to save something worth keeping with save_to_swipefile. Findings marked seed:true are NOT new launches — the first check of a brand has nothing to diff against, so it seeds the board with what that brand is running right now; only later runs surface genuine changes. Read-only and free — it returns the stored results of past runs and never triggers a check (set_competitor_watch({runNow:true}) is what runs one).

list_whatsapp_accounts

ChatGPT
The WhatsApp Business Accounts SHARED WITH THIS PROFILE and the phone numbers registered on each — the ids every other WhatsApp tool needs, plus each number’s QUALITY RATING, which is what decides how many messages Meta will let it send. Start here. A WABA with no number cannot send anything and the reply says so: Hermoso does not register or verify numbers, that is WhatsApp Manager. A business portfolio that could not be read is REPORTED rather than dropped — an account missing from this list would read as "the brand has no WhatsApp", which is a claim about their business and not about our read. TWO ACCOUNTS CAN CARRY THE SAME DISPLAY NAME — always quote the display field or the id when naming one to a user, never the bare name. ONLY THE ACCOUNTS SHARED WITH THIS PROFILE ARE REACHABLE. One Meta login often administers WhatsApp for several businesses, so a human ticks which belong to this profile under Settings > Connectors > Meta > Manage accounts (or set_connector_accounts(provider:"meta")); nothing else here can see or touch the rest. With none ticked every WhatsApp tool refuses and names that as the way out. With exactly ONE ticked, wabaId is optional — pass it only to disambiguate. Read-only, 0 credits (WhatsApp conversations are billed by META to the business directly, never in Hermoso credits). Needs Meta connected.

list_whatsapp_templates

ChatGPT
The message templates on one WhatsApp Business Account with the REVIEW STATUS Meta actually holds for each. THE STATUS IS THE POINT: an APPROVED template is the only thing WhatsApp accepts outside the 24-hour customer-service window, and Meta’s own refusal for anything else says "the template does not exist in the specified language or the template has not been approved" — two completely different problems in one sentence. Each row also reports its LANGUAGE, because Meta treats en and en_US as different templates and sending against the wrong one is refused as if it were unapproved. Each row also reports the VARIABLES the template carries — the distinct {{1}}, {{2}} placeholders per component, with the total — because send_whatsapp_message must supply EXACTLY that many and Meta refuses a mismatch with 132000. Read the count off that field, never off the one-line body preview, which is capped. Read-only, 0 credits. Needs Meta connected.

list_x_ads_accounts

ChatGPT
List the X (Twitter) ad accounts this profile can act on, with the PERMISSION LEVEL held on each so you can tell an admin grant from a read-only one before attempting a write. X grants API access PER AD ACCOUNT, not per app: the customer adds Hermoso’s X user to their ad account at business.x.com -> Account access, and it appears here. Read-only, free.

list_x_ads_campaigns

ChatGPT
List campaigns on an X ad account — status, budgets, and whether X considers each servable. Omit accountId when only one account is reachable and it resolves itself. Read-only, free.

list_x_ads_funding_instruments

ChatGPT
List the funding instruments (payment methods) on an X ad account — type, currency, credit limit, credit remaining, and whether each can currently fund a campaign. THIS IS THE ANSWER TO “why is my X campaign not delivering?” whenever the cause is a cancelled card or an exhausted credit line, which is invisible from the campaign itself, and it shows what a campaign will spend against BEFORE anyone activates it. Hermoso cannot add a payment method — that is done at ads.x.com. Read-only, free.

list_x_ads_line_items

ChatGPT
List the line items on an X ad account — X's name for an ad group, and the level that carries the objective, the placements, the bid, the targeting and the creatives. Pass campaignId to scope it to one campaign. servable is X's own verdict on whether the line item could run. Read-only, free.

list_x_ads_promoted_tweets

ChatGPT
List the promoted posts (the CREATIVES) attached to an X line item, or across the whole ad account. Two things only this can tell you: each post's APPROVAL STATUS, so an ad X rejected — which can never serve however the statuses are set — is visible rather than mysterious; and the promoted-tweet ID, which is the only way to remove one. NOTE a promoted post cannot be PAUSED on X (its PUT accepts only an approval appeal), so the reversible way to stop it is to pause its LINE ITEM. Read-only, free.

list_x_ads_targeting

ChatGPT
List every targeting criterion an X line item carries, WITH THE ID of each — the id needed to remove one with delete_x_ads_object. READ THE EMPTY CASE CORRECTLY: no targeting criteria on X means the line item is UNRESTRICTED and will reach the broadest possible audience once ACTIVE — it does NOT mean it cannot serve. Read-only, free.

list_x_broadcast_chat_moderators

ChatGPT
Use this when you need the brand’s persistent X broadcast chat moderators, who moderate every current and future broadcast. Read-only. Needs X connected; X bills per call, so it costs credits.

list_x_broadcasts

ChatGPT
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, recurring series). 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).

list_x_dms

ChatGPT
Read the direct messages sent to the connected X account and group them into conversations, newest first — who is waiting on a reply, in their own words. Use it for 'any new DMs on X', 'what did they say', 'anything I need to answer'. Reply with send_x_dm. COSTS CREDITS PER MESSAGE RETURNED plus one account lookup, so keep maxResults small (default 10) and tell the user the cost before pulling a big page. X KEEPS 30 DAYS of direct-message history and offers no date filter, so a short list can mean an old conversation rather than a quiet inbox — say so rather than reporting silence. There is no conversation-list endpoint at X: this builds the thread list by reading events and grouping them, so a conversation whose last message is older than the page you pulled will not appear. Needs X connected, AND an X account connected before direct messages shipped must be reconnected once (Settings > Connectors > X) — X only grants permissions at authorize time.

list_youtube_captions

ChatGPT
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.

list_youtube_categories

ChatGPT
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.

list_youtube_channel_sections

ChatGPT
Use this when you need the shelves on the connected channel’s homepage, in order, with their ids. YouTube’s list can lag a recent change by a few seconds. Read-only, free.

list_youtube_comments

ChatGPT
Read the comments under a video on the connected channel — the questions, objections and exact wording real viewers use. Same raw material for ad copy that list_meta_comments gives you on Meta. Returns author, text, like count, timestamp, reply count and comment id, newest first — plus imageUrl when a viewer attached an image or GIF to the comment (a signed YouTube link that expires six hours after the read, so download it then if it needs keeping). Read-only, 0 credits. Needs a connected YouTube channel.

list_youtube_playlist_images

ChatGPT
Use this when you need the custom cover image on a playlist and its imageId. Read-only, free.

list_youtube_playlists

ChatGPT
List the playlists on the connected YouTube channel — id, title, description, privacy and video count. Pass a playlistId to get that ONE playlist plus its entries in order. IMPORTANT: each entry carries BOTH a videoId and an itemId; itemId is the playlist-ENTRY id and is what add_video_to_youtube_playlist / remove_youtube_playlist_item / move_youtube_playlist_item needs to remove or re-order a row, because one video can appear in a playlist more than once so a videoId does not identify the entry. Read-only, 0 credits. Needs a connected YouTube channel.

list_youtube_report_jobs

ChatGPT
The YouTube BULK reporting jobs running on this channel — which report each one generates, its report type id, and when it was scheduled. Call this to find out whether thumbnail-CTR history is already accumulating, and since when, BEFORE promising a user a number: the bulk API can only answer about days after a job existed. Read-only, 0 credits.

list_youtube_video_stats

ChatGPT
Views, likes and comment counts for up to 50 YouTube videos IN ONE CALL, which is how to answer "how are my last twenty uploads doing" without one youtube_video_insights per video. Pass videoIds from list_youtube_videos. IT CARRIES NO TITLES, and that is the resource rather than a bug: VideoStatsSnippet publishes only publishTime, so join on videoId with list_youtube_videos when a name is needed. YouTube calls this endpoint "intentionally not atomic", so a short answer is normal: a video that is private, deleted, or not visible to the connected account simply does not come back, and this tool names the missing ids. Never report a missing id as zero views. Read-only, free.

list_youtube_videos

ChatGPT
List the connected channel’s OWN recent uploads — video id, title, publish date and privacy — so you can resolve a video WITHOUT asking the user for a link. Call this whenever the user names a video loosely ("my latest", "the shorts one", part of a title) and match it yourself; only ask them when two titles are genuinely ambiguous. This is the tool that gets you the videoId every other YouTube tool needs — youtube_channel returns counts only, and search_youtube searches the PUBLIC index, not your uploads. Includes UNLISTED and PRIVATE videos, which are invisible to any public search. Read-only, 0 credits. Needs a connected YouTube channel.

localize_ad

ChatGPT
Translate the on-image text of ONE finished static ad into other languages and keep everything else: same picture, layout, typeface, colours, logo and product. Pass image and languages (up to 5, e.g. ["Spanish", "German", "French (Canada)"]). The ad's text is read (3 credits), translated the way a native copywriter in each market would write it (brand and product names, URLs and prices kept as written), then one image edit per language; each output is proofread and flagged (textCheck) if the words do not match, never silently re-rendered. When the saved brand has a product photo and the ad shows that product, the photo rides with the edit and the product's label is read and checked against it: re-printed from the photo only when it came out wrong (the check alone is charged when it is right; one small extra charge finds the product first). fixLabel:false turns that off. Priced before it runs. For a VIDEO use dub_video.

lock_bluesky_convo

ChatGPT
Use this when no new messages should be sent in a Bluesky DM conversation. Reversible with unlock_bluesky_convo. Free.

make_explainer

ChatGPT
AI HOST EPISODE: format 'host_episode' = one presenter talking to camera, the camera changing every piece (frontal, three-quarter, close), the same host and set throughout, native voice, 16:9. Host = creator (saved or preset) or hostImage; words = topic (written for you) or script (verbatim). It returns a 480p DRAFT; HD (720p) is a SEPARATE call with fromDraft (quote it with dryRun, run it only when the user asks). Otherwise: turn a TOPIC into a finished narrated explainer video, in one of TWO LANES (lane). 'blocks' (the default) = 10-second VIDEO blocks, one narrated line per block, hard cuts, a music bed under the voice: an EXPLAINER renders on Gemini Omni at 720p (9:16 by default, or 16:9), a FACELESS CHANNEL video (format:'faceless_channel', or channel history / kids / fairytale) on MiniMax H3 at 2K (16:9 by default) with five cuts per block. 'stills' = a picture film: writes a sectioned script, paints a BURST of pictures per section (about one every 1.5s — most of them one-detail edits of the frame before, so it reads as movement rather than a slideshow), narrates each section with TTS, holds each picture PERFECTLY STILL for its own slice of the narration (the motion is the CUT RATE — a slow move on a still shimmers), then composites the end card (and any on-screen text you asked for) with the Chrome+ffmpeg engine the ads use (text is never model-painted, so it never garbles). BURNED ON-SCREEN TEXT IS OFF BY DEFAULT — the narration carries the point and the pictures carry the story, so the film ships clean unless the user asks otherwise; captions:true adds held key points and subtitles:true adds narration-timed CAPS (see both). The stills lane is an image film WITH motion, not N video-model renders — that's what keeps it affordable. style picks the visual family: the default 'cinematic' is photoreal editorial; every other id is a STYLED, strictly non-photoreal look (illustrated / collage / clay / pixel …) that first renders ONE style-key image and then locks every scene to it, so the whole film holds one look. Cost at the default frame density: a ~130-credit hold for a 60s explainer on the default style, ~100 styled; frameDensity:'lean' roughly halves it and 'minimal' (one picture per section) is ~30. All settle to the exact per-frame image + narration spend (a longer target = more sections = more). Takes SEVERAL minutes — one image render per frame; independent frames are painted concurrently, so it is far faster than the frame count suggests. Needs the writing model and a narration voice engine connected. NOT the tool for a short product ad — use render_ad or generate_video for those, and make_template_ad for the deterministic native formats.

make_insert

ChatGPT
A reaction picture (image, or video from videoStart) with its sound, cut to when the sound lands (or seconds): a 1080x1920 clip for post_edit join.

make_template_ad

ChatGPT
Use this when the user asks for a quick, low-cost ad or post built from their words and pictures: a slideshow, carousel, iMessage or ChatGPT chat, Notes, kinetic type, myth vs fact, an app tour, or their own HTML design. Rendered from HTML, no AI model, ~30s, a couple of credits, ready to post. Presets are SHORTCUTS; 'custom' is YOUR OWN design as config.html (+ css), so no layout, type, colour or motion is unsupported. custom: { html, css?, size? ('9:16' default | '4:5' | '1:1' | '16:9' | any 'W:H' | {w,h} px), durationSeconds? (1-60 = VIDEO; CSS/SVG animation and <video> are frame-stepped, scripts stripped), slides?:[{html, css?}] (2-35 = carousel) }; {{logo}} {{brandName}} {{domain}} {{accent}} fill from the brand; images and fonts load by public https URL; notes[] lists what failed to load. YOU write preset copy: short, casual, believable, finished phrases within budget. Preset ids and their fields are listed on config. config.music on a VIDEO: omit for a free library bed matched to its mood when the library is stocked (hermoso_capabilities hasMusic; else only its own sound effects), 'off' for silence, or words (a mood or description) to compose one for a flat fee.

make_thumbnail

ChatGPT
Render a click-driving YOUTUBE / Shorts / Instagram THUMBNAIL or video cover through the full production pipeline (concept, casting, scene, render, tweaks, text), not a bare image prompt. Use it for any "thumbnail", "video cover" or MrBeast-style packaging ask INSTEAD of generate_image. About 9 credits per variant; the headline overlay is free. CONCEPT — open an INFORMATION GAP (the image raises a question the title answers) while staying truthful to the video. Brainstorm ≥5 concepts across the 16 frameworks (ids on framework; combining two is fine) before you pick; hermoso_capabilities has each one's 'realize it with' note and the emotion, overlay, font and rim-colour catalogs. THREE GATES, all BEFORE you render: 1. WHO IS IN FRAME — never assume or silently substitute a stranger. A framework with a person and no face photo is refused (nothing charged): ask the user once — themselves (a face photo, identity-locked), a generated person (castGenericPerson:true), or a people-free framework. 2. TEXT — default is a CLEAN render with the headline TYPESET over it (free, legible, correctly spelled): pass headline. bakeText:true only on an explicit ask for words painted INTO the image. Never infer text intent from the topic. 3. HOW MANY — ask once: one, or a SET (offer 4: one concept at different emotions / camera takes). Default 1; variants caps at 16. emotion is the biggest CTR lever on a face (identity lock is automatic for every face photo). To fix a finished one, re-call with tweak + sourceImage for a surgical edit (emotion / background / background_color / rim_light) — tweaks chain. ALWAYS check the returned postRenderCheck against the image before presenting it. PROMPT LANGUAGE — write every DESCRIPTIVE field (sceneBrief, keyElements, location, composition, background, topic, each person's describe, every reference) in ENGLISH, translating the user's words: the models render English better. headline, headlineLines and bakedUiText stay verbatim in the user's language.

manage_instagram_product_tags

ChatGPT
READ the product tags on a post the brand has already published, or ADD/MOVE tags on it. Pass tags to update: Meta's own behaviour is "updates coordinates if the product is already tagged; otherwise adds new tag" — so it is ADD-OR-MOVE, never replace-all, and it CANNOT be used to take a tag off. THERE IS NO WAY TO REMOVE A PRODUCT TAG: Meta documents Creating, Reading and Updating on this edge and no delete at all, so Hermoso will not guess at one — deleting the post is the only thing that removes its tags, and the reply says so rather than implying otherwise. The answer is always READ BACK from Instagram, and it separates tags that are STORED from tags that will actually be SHOWN — only an "approved" product ever appears on a published post. Read is free; the update costs 0 credits too. Whether Meta has approved Hermoso's app for the permission this needs is read LIVE from Meta: when it has not, list_connectors says so under Meta and this tool's refusal says Meta hasn't approved Hermoso for it yet. That is Meta's decision about Hermoso, not the user's connection, so never tell them to reconnect for it.

manage_meta_partnership_creator

ChatGPT
Add a creator to — or remove one from — this brand’s Partnership Ads lists. Name at least one list: allowAds (whose content this profile may RUN AS AN ADVERT) and/or allowTagging (who may TAG this brand as a paid partner on their own post). NEITHER DEFAULTS ON, because one of them lets somebody attach this brand’s name to their own post. ADDING IS A REQUEST, NOT AN APPROVAL: Meta’s own name for the operation is "send permission request", the creator accepts on their own Instagram account in their own time, and until the read-back reports approved this profile cannot advertise their content. Never tell anyone a partnership is live off the back of this call — report the status the reply carries. THE OUTCOME IS READ BACK OFF META’S OWN LIST, NEVER TAKEN FROM THE WRITE’S REPLY — because Meta answers the ADD with HTTP 500 (code 1, “Please reduce the amount of data you’re asking for”) while performing it, measured 4/4 on 2026-08-25. If this reply carries metaWriteError alongside a successful summary, that error is Meta noise about a write that HAPPENED: do not retry, and do not tell the user it failed. The reply’s verdict is one of applied / already / unchanged / unknown, and unknown means the list could not be re-read — which is not the same as nothing having changed. REMOVING FLIPS A STATUS TO “Canceled”; it does not delete the row. One tool does both lists because Meta removes from them with two DIFFERENT verbs (DELETE on one, a revoke flag on the other) and a caller who assumes one silently does nothing on the other. 0 credits.

manage_microsoft_merchant_catalog

ChatGPT
Create, update or delete a catalog in a Microsoft Merchant Center store. Creating needs name + market + isPublishingEnabled; updating needs catalogId + name + isPublishingEnabled (market is not settable after creation). isPublishingEnabled is NEVER defaulted on — products serve only when it is true, so publishing a catalogue is always an explicit choice. Names must be unique within the store and are capped at 70 characters. Pass deleteIt:true with catalogId and confirm:true to remove one. Every outcome is READ BACK from Microsoft. 0 credits.

mark_bluesky_convo_read

ChatGPT
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.

mark_x_chat_read

ChatGPT
Use this when a conversation should be marked read. The other person may get a read receipt. Needs X connected; X bills per call, so it costs credits.

merchant_account_status

ChatGPT
WHY THE MERCHANT CENTER ACCOUNT IS OR IS NOT SERVING, in one read. Do not trust the program state on its own: an account can report Shopping ads and free listings as ENABLED and serve in ZERO countries, because Google counts a region as active only where every requirement for that program is met. This reports the active regions, the unmet requirements Google names with its own help link for each, and then the eight account settings that explain them — whether the homepage is CLAIMED (unclaimed stops the whole account serving), whether business info has an address, a verified phone and a customer service contact, whether any active shipping service covers the countries you sell to, the return policies, whether the Merchant Center terms have been accepted, plus autofeed and automatic improvements. An account with no shipping settings at all is reported as not configured, which is an account state and not a failed read. Anything that genuinely could not be read comes back null with a warning, which means unknown rather than missing. Read-only, free.

merchant_quota

ChatGPT
Merchant Center API quota and account limits, which is the difference between "the API refused" and "you are out of daily quota". Two different things in one answer: quota groups are how many API CALLS each method group has left today, and account limits are how many PRODUCTS the account may hold per destination. Google resets the daily quota at 12:00 PM MIDDAY UTC, not at midnight. If one half of the read fails it comes back null with a warning, which means unknown rather than zero. Read-only, free.

merchant_report

ChatGPT
COMPETITOR INTELLIGENCE GOOGLE COMPUTES FOR FREE, for any retail brand with a Merchant Center. Ten report views, read with a SQL-like MCQL query. The three worth reaching for first: competitive_visibility_competitor_view (WHICH other domains appear beside this merchant, their rank, page-overlap and higher-position rates — i.e. who is actually beating them), best_sellers_product_cluster_view (what is SELLING on Google in a category right now, with an inventory_status saying whether this merchant even stocks it) and price_insights_product_view (Google's own suggested price plus the predicted click and conversion change). Also: competitive_visibility_benchmark_view, competitive_visibility_top_merchant_view, best_sellers_brand_view, price_competitiveness_product_view, product_view, product_performance_view, non_product_performance_view. MCQL IS NOT SQL: no OR, no subqueries, no GROUP BY, no aggregates, no JOIN, and ORDER BY may only name fields already in SELECT. Date filters use WHERE date BETWEEN '2026-01-01' AND '2026-01-31' or WHERE date DURING LAST_30_DAYS. Several views REQUIRE specific fields in SELECT and in WHERE — competitive visibility needs report_category_id + report_country_code + traffic_source, and top_merchant uniquely REQUIRES a date condition while FORBIDDING date in SELECT. An unknown view is refused by name with the list. NOT AVAILABLE ON MULTI-CLIENT (MCA) ACCOUNTS — pass a subaccount id. An empty result is often a normal state (price insights are only produced where Google predicts a substantial gain), and the note says which kind of empty it is. Read-only, free, no new permission.

merge_openai_ads_audiences

ChatGPT
Combine 2 to 64 existing ChatGPT Ads custom audiences into ONE new audience. The SOURCE audiences are left unchanged, later updates to them do NOT propagate into the merged one, and no existing campaign switches to the new id by itself — re-point targeting deliberately if that is the intent. Creates a new audience; changes nothing that is already serving.

meta_catalog_blast_radius

ChatGPT
Read what is INSIDE a product catalog — how many products, which product sets — and get the exact sentence describing what deleting it would destroy. Free and read-only. CALL THIS BEFORE PROPOSING A DELETE: deleting a catalog is permanent and Meta offers no archive and no undo, so naming the cost should not require aiming the destructive call at the target and reading its refusal. It also gives you the catalog’s exact name, which delete_meta_catalog requires echoed back. 0 credits.

meta_insights

ChatGPT
Pull performance INSIGHTS (spend, impressions, reach, clicks, CTR, CPC, CPM, conversions) for a connected ad account, or a specific campaign / ad set / ad. Pass adAccountId (for auth); optionally objectId to scope to one object and level to break the numbers down. BREAKDOWNS are what make the numbers actionable — a flat total says an ad cost $X, never WHO it worked on: pass breakdowns:"age,gender", "publisher_platform,platform_position" (which placement), "country" / "region" / "dma" (where), "impression_device" / "device_platform" (what they held). Comma-separated; "placement", "device" and "geo" are accepted as aliases; an unknown value is REJECTED, never silently ignored. THREE breakdowns need an ad-account OPT-IN from 2026-08-06 — impression_device, hourly_stats_aggregated_by_audience_time_zone and frequency_value: Meta returns NO ROWS (not an error) for an account that has not opted in, so they are always ATTEMPTED, and if nothing comes back the report is re-run WITHOUT them and droppedBreakdowns + a note name the missing dimension and say an account admin can enable it in Ads Manager. A dropped dimension is ABSENT, never zero — never present the remaining total as if it were still split by it. Date window: datePreset OR since+until (YYYY-MM-DD). datePreset is Meta's OWN enum — today, yesterday, last_3d, last_7d, last_14d, last_28d, last_30d, last_90d, this_week_mon_today, this_week_sun_today, last_week_mon_sun, last_week_sun_sat, this_month, last_month, this_quarter, last_quarter, this_year, last_year, maximum, data_maximum. THERE IS NO "lifetime": Meta disabled it in Graph API v10.0 and replaced it with "maximum" (the last 37 months); anything unrecognised is refused by name here rather than 400ing at Meta. Read-only.

meta_page_insights

ChatGPT
Organic performance for the brand’s connected Facebook Page — views and unique reach (page_media_view / page_total_media_view_unique, Meta’s own replacements for the impressions family it retired), post engagements, video views, daily follows, plus follower and Page-like counts — with the linked Instagram account’s headline numbers alongside. This is ORGANIC reach; use meta_insights for paid ad performance, and instagram_insights for the full Instagram set and its audience demographics. Any metric Meta returns no value for is named as MISSING data, which must never be reported as zero.

meta_post_insights

ChatGPT
Performance for a single organic post — on Facebook views/reach (post_media_view, post_total_media_view_unique — Meta’s own replacements for the retired impressions family), clicks, reactions and video watch time; on Instagram views, reach, likes, comments, saves, shares, total interactions and (where the media type has them) follows, profile visits, story navigation and reel watch time. Use it to find which organic posts earned their reach before turning one into a paid ad. A metric Meta returns no value for is reported by name as MISSING — never read it as zero.

meta_webhook_status

ChatGPT
What Meta ACTUALLY has recorded for this app on a Page — the fields it will push, read straight back from Meta rather than from anything we stored. CHECK THIS BEFORE CONCLUDING THAT WEBHOOKS ARE NOT WORKING: an unsubscribed Page is completely silent and looks identical to a quiet one, so an empty event feed is not evidence of anything until you have seen the subscription. Read-only, 0 credits.

microsoft_ads_auction_insights

ChatGPT
Who ELSE is competing for the same Microsoft Advertising auctions — the other advertisers’ domains with their impression share, overlap rate, position-above rate, top-of-page rate and outranking share. microsoft_ads_report tells you what the account did; this tells you who it did it against, which is the input to a bid or a creative decision rather than a report. Scope it with entityType Account (default, the whole account) / Campaign / AdGroup / Keyword plus entityIds (max 200), and optionally segment by up to three of Day, DayOfWeek, Device. SAY THIS WHEN REPORTING: every figure is a share of the auctions YOUR ads entered, never a measure of a competitor’s whole account or budget — a competitor missing from the list did not necessarily not bid. An empty result means there was no delivery to compare in that window, which is MISSING data; never report it as "nobody is bidding against you". Read-only, 0 credits.

microsoft_ads_auto_apply

ChatGPT
Whether Microsoft is allowed to change this ad account BY ITSELF, unattended: the auto-apply opt-in status for each of the five recommendation types Microsoft supports. THIS IS THE READ THAT ANSWERS "is something changing my account when nobody is looking?", and it is worth running on any Microsoft account you inherit: an account can already be opted in without anyone at the brand having done it, and there is no other way to find out from here. A type Microsoft returns no status for is reported as UNKNOWN, never as off. TWO THINGS THAT CATCH PEOPLE OUT AND ARE STATED IN THE REPLY: these five type names are a DIFFERENT, case-sensitive vocabulary from the six microsoft_ads_recommendations uses (Microsoft cannot auto-apply a budget recommendation at all, there is no such type), and three of the five have no readable recommendation, so there is no way to preview what auto-apply would do for those. Read-only, 0 credits.

microsoft_ads_budget_opportunities

ChatGPT
Where a Microsoft Advertising campaign is BUDGET-CONSTRAINED — Microsoft’s own recommended budget against the current one, the estimated WEEKLY click and impression gain from raising it, and a budget/return curve. Omit campaignId for the whole account. SAY THIS WHEN REPORTING: these are Microsoft’s FORECASTS, never measurements — a projected increase has not happened — and acting on one spends real money, so it takes set_microsoft_ads_budget and an explicit yes from the user. Microsoft EXCLUDES user-paused campaigns from this analysis, so a paused campaign is absent by design rather than well-funded. Read-only, 0 credits.

microsoft_ads_recommendations

ChatGPT
What Microsoft Advertising ITSELF suggests changing on the account: budget raises, new keywords, broad-match widenings, conflicting negative keywords to remove, and responsive search ads or extra headlines Microsoft has written. Each row carries the campaign and ad group it touches plus Microsoft’s OWN estimate of what applying it would add to clicks, impressions, conversions and COST; a budget recommendation states the current and the recommended daily amount, so the money is a vendor fact rather than a guess. SAY THIS WHEN REPORTING: every Microsoft recommendation INCREASES what the account buys, that is what they are for, so none of them is a free win, and an empty list means Microsoft has no ADVICE (usually too little delivery to compute any), never that the account is optimal. Dismissed recommendations are hidden unless you ask for them. Read-only, 0 credits.

microsoft_ads_report

ChatGPT
Performance for a Microsoft Advertising account — impressions, clicks, CTR, average CPC, spend, conversions. reportType picks WHICH report, and that is the whole Microsoft reporting surface, not just campaigns: AdGroupPerformance, AdPerformance, KeywordPerformance, SearchQueryPerformance (the actual search terms people typed), GeographicPerformance, UserLocationPerformance, AgeGenderAudience and ProfessionalDemographicsAudience (LinkedIn-sourced job function and industry, inside Bing), ConversionPerformance, DestinationUrlPerformance, ShareOfVoice, AssetPerformance, ProductDimensionPerformance, SearchCampaignChangeHistory ("what changed on Tuesday") and ~30 more — an unknown name is refused WITH the full list rather than forwarded. aggregation controls the row grain (Summary / Daily / Hourly / Weekly / Monthly / Yearly / HourOfDay / DayOfWeek). Two reports keep far less history than the usual 36 months — AssetPerformance 30 days, ShareOfVoice 6 — and the reply says so, because an empty short-retention report is a retention limit, not an absence of delivery. Window via timePeriod (Today | Yesterday | LastSevenDays | Last14Days | Last30Days | ThisWeek | LastWeek | LastFourWeeks | ThisMonth | LastMonth | LastThreeMonths | LastSixMonths | ThisYear | LastYear | ThisWeekStartingMonday | LastWeekStartingMonday | LastFourWeeksStartingMonday) or since+until (YYYY-MM-DD) — default Last30Days. An unrecognised timePeriod is REFUSED, never silently swapped for another window. Microsoft generates reports ASYNCHRONOUSLY: this can return pending:true with a reportRequestId, and you must call again rather than reporting any numbers. A report that succeeds with ZERO rows genuinely means there was no delivery in that window — say exactly that; never present zeros as measured performance. Read-only, free.

microsoft_ads_traffic_estimates

ChatGPT
What a set of keywords would DELIVER on Microsoft Advertising at a given bid — estimated impressions, clicks, CTR, average CPC, average position and total cost. maxCpc is REQUIRED because a traffic estimate IS a function of the bid; estimating without one would be inventing the input. locationIds is REQUIRED for the same reason a search volume needs a market. SAY THIS WHEN REPORTING: Microsoft returns a MINIMUM and a MAXIMUM per keyword — quote the range, never average the two into a single figure — and every number here is a FORECAST, so never present it as measured performance. Read-only, 0 credits.

mine_angles

ChatGPT
Mine ad ANGLES from real customer language: gathers the customer's own words (Reddit, TikTok, the brand's review page + review-site results) and returns a RANKED angle bank — each angle tagged (pain / outcome / identity / fear / competitive-displacement / social-proof / contrast), 2-5 VERBATIM proof quotes, a 0-100 score with breakdown, and a ready-to-run hook in the customer's own voice. Reads YOUR saved brand (pass brandId to target a specific profile — that switches this key's active profile like use_brand). To tear down a COMPETITOR use competitor_teardown instead. YOUR OWN REVIEWS: pass reviews (a list of review texts, or one pasted block: one per line, numbered, blank-line separated, or a CSV with a review column) and/or reviewsUrl (a CSV, TXT or JSON file from upload_file, or a review page; on a local CLI a file path works too). They are first-class evidence: every quote from them is checked word for word against what you sent and labelled 'your reviews', and a quote that is not verbatim is dropped and counted. useOwnReviewsOnly:true mines only your reviews and gathers nothing public. Limits: 300 reviews, 2,000 characters each, 40,000 in total; over that it is refused at no cost, so send fewer or split into batches. Each angle comes back with next: the exact plan_variations and generate_image arguments that turn it into finished statics (one generate_image per angle = statics with distinct angles). Spends a few credits.

mixpanel_funnel

ChatGPT
A saved Mixpanel funnel. Call with NO id to LIST the saved funnels and their ids; call with funnelId to read its conversion data. Warning: MIXPANEL HAS PUT THE FUNNELS QUERY API IN MAINTENANCE MODE — their words: "We recommend discontinuing new use of this endpoint. To get funnel data, build a Funnels report in-app and query it programmatically with the Insights Query API." So prefer mixpanel_insights with that report's bookmark id; this is offered because it still answers and because it is the only way to LIST a project's funnels. length IS BOUNDED AT 90 DAYS, WHICH IS NOT THE NUMBER 90: it counts lengthUnits, so 90 days is 2160 hours or 129600 minutes, and an over-long window is REFUSED BY NAME rather than trimmed — silently shortening it would answer a different question with nothing to show that it had happened. Omit both and Mixpanel uses whatever the funnel was saved with in its own UI, which is usually what you want. Dates are YYYY-MM-DD and BOTH ENDS ARE INCLUSIVE, resolved in the PROJECT's timezone (UTC unless its owner changed it) rather than in yours. 60 queries/hour across the whole Query API (5 concurrent) — the tightest budget of any connector here, so widen a range rather than looping over days. Read-only, 0 credits.

mixpanel_insights

ChatGPT
READ BACK A SAVED MIXPANEL REPORT by its bookmark id — and this is the PREFERRED Mixpanel lane, not a fallback. Mixpanel has put BOTH its Segmentation and its Funnels query APIs in maintenance mode and recommends in their place that the report is built in the Mixpanel UI and read programmatically through Insights, which is exactly what this does. THE BOOKMARK ID MUST COME FROM THE USER: Mixpanel publishes no endpoint that lists saved reports, so ask them to open the report in Mixpanel and copy the id out of its URL (the part after "report-"). Remember Mixpanel allows only 60 QUERIES PER HOUR across its entire Query API — the tightest budget of any connector here — so reuse an answer rather than re-asking. READ BACK dateRange, do not assume the window: a saved report's date range is configured in Mixpanel's own UI and not by this call, and it comes back stamped with the PROJECT's UTC offset — the only place this connector can observe that timezone at all. Read-only, 0 credits.

mixpanel_retention

ChatGPT
MIXPANEL RETENTION — how many of the people who did a first thing came back and did another, cohorted by day / week / month. This endpoint is FULLY SUPPORTED, unlike segmentation and funnels, which Mixpanel has put in maintenance mode — so it is the one typed Mixpanel report to reach for first. retentionType "birth" cohorts people by their FIRST occurrence of bornEvent (new-user retention, and Mixpanel's DEFAULT); "compounded" counts anyone active. Warning: MIXPANEL REQUIRES bornEvent WHENEVER retentionType IS "birth", AND BIRTH IS THE DEFAULT — so a call with neither is refused HERE, for free, rather than spending one of the sixty hourly queries on their 400; use list_mixpanel_events first to name a real event. TWO FILTERS, NOT ONE: bornWhere filters who ENTERS the cohort, where filters the RETURNING event. And interval is the WIDTH of each bucket while intervalCount is HOW MANY of them — different knobs. Dates are YYYY-MM-DD and BOTH ENDS ARE INCLUSIVE, resolved in the PROJECT's timezone (UTC unless its owner changed it) rather than in yours. 60 queries/hour across the whole Query API (5 concurrent) — the tightest budget of any connector here, so widen a range rather than looping over days. Read-only, 0 credits.

mixpanel_segmentation

ChatGPT
One Mixpanel event over time, optionally broken down by a property (on) and filtered (where). Warning: MIXPANEL HAS PUT THIS ENDPOINT IN MAINTENANCE MODE — their words: "We recommend discontinuing new use of this endpoint. To break down and filter event data, build an Insights report in-app and query it programmatically with the Insights Query API." It still answers today, which is why it is offered rather than withheld, but SAY SO when you use it and prefer mixpanel_insights whenever the user can build the report. It takes ONE event name and not an array (the /events endpoints take an array; this one does not — an easy and silent mistake). 60 queries/hour across the whole Query API. Read-only, 0 credits.

moderate_tiktok_ads_comment

ChatGPT
THE MODERATION TOOL for TikTok ads, and the TikTok twin of hide_meta_comment / unhide_meta_comment / delete_meta_comment. Hiding takes a comment out of public view — everyone stops seeing it except the person who wrote it, who is never told — and it works on ANYONE’s comment, which is what distinguishes it from delete_tiktok_ads_comment (that one only ever removes your own reply). IT IS FULLY REVERSIBLE: the same tool with operation PUBLIC puts a comment back, which is why it needs no confirmation. Takes a list, so a whole batch of spam goes in one call. TikTok answers this endpoint with no data at all, so the reply reports what was ACCEPTED and tells you to re-run list_tiktok_ads_comments to see commentStatus change — it is not a read-back and does not pretend to be. To hide a whole class of comment automatically and permanently, add the word to the account’s blocked-word list instead (manage_tiktok_ads_blocked_words).

move_youtube_playlist_item

ChatGPT
Use this when an entry should move to a new position in its playlist. Takes the itemId (from list_youtube_playlists with a playlistId); position is zero-based. Free.

multiply_ad

ChatGPT
MULTIPLY a winning video ad into N variants: each gets a NEW character, outfit, location and/or objects while the cut, the camera motion, the pacing and the ORIGINAL AUDIO stay exactly as they were (that is what made the ad work), and any burned-in captions are removed. Pass the source video URL (a previous render, a job result, list_library, or the top performer from post_performance / meta_insights). HOW: the source video itself DRIVES each variant (motion transfer from one image-edited opening frame), so every variant comes back the SAME LENGTH as the source with the same cut, the same performance and the original audio — only the person, outfit, set and props change. Sources up to 30 seconds work as they are; longer ones are refused for free with the way out (trim it first: post_edit with ops [{op:'trim', start:0, end:30}] — clip_video is the AI highlight clipper, not a trim). Returns the plan and ONE JOB PER VARIANT — call get_job on each until it reports done; do not describe a variant before its URL arrives. Cost is quoted per variant in the reply (use dryRun:true to see the plan and the quote without rendering). Regions: pass regions:['Berlin','Tokyo'] to restyle variants per market; translation is a separate, explicit step — dub_video on a finished variant. change is what should change, in the user's own words ('older women', 'winter streets', 'swap the mug for our bottle'): every variant applies it and the variants still differ around it; a part of it about the voice, words, language, music, length or captions cannot change here, and the reply says so and names the tool that can.

mute_bluesky_convo

ChatGPT
Use this when a Bluesky DM conversation should stop notifying the account. Free.

mute_x_broadcast_chat_user

ChatGPT
Use this when a viewer should be muted in the brand’s live broadcast chat. Pass userId or username; until ends a timeout (omit to mute for the broadcast); messageId also removes that message. Needs X connected; X bills per call, so it costs credits.

mute_x_chat

ChatGPT
Use this when one of the brand’s X Chat conversations should stop notifying it. Only the brand’s own view changes. Get id from list_x_chats. Needs X connected; X bills per call, so it costs credits.

openai_ads_bulk_job

ChatGPT
Poll a ChatGPT Ads bulk job and read the result of EVERY operation in it. Reports the job status (pending, in_progress, completed, partially_failed, failed — the last three are terminal) plus a per-operation verdict: created, updated, validated, failed or skipped, each with the new resource id or the error. THE VERDICT COMES FROM THE OPERATIONS, NEVER FROM THE JOB STATUS — "the job finished" and "your ad was created" are different questions, and a partially_failed job answers yes to one and no to the other. While a job is still running the results are an INCOMPLETE SNAPSHOT and there is no cursor to page with; once complete is true, page with after set to the last operation_id. A failed operation that reports retryable names retry_after_seconds — reuse the ORIGINAL create idempotency_key when resubmitting or the retry creates a duplicate. Free, read-only.

openai_ads_conversions

ChatGPT
ATTRIBUTED CONVERSIONS for ChatGPT Ads — the number the whole pixel + conversion-event setup exists to produce, beyond what openai_ads_report shows: that report carries conversions and CPA for the account, a campaign, ad group or ad, while this tool adds the click-through / view-through split, totals per id across many ids, and the received-event sample. Pass entityIds — the campaign / ad group / ad ids to report on — with a matching level; the default window is the last 30 days. NEVER ADD viewThroughConversions TO conversions: since OpenAI’s 2026-09-30 change conversions already counts click-through PLUS view-through goal conversions within the chosen windows (their example: 3 click-through and 2 view-through give conversions 5), and the rows and totals carry clickThroughConversions and viewThroughConversions as its two parts. Post-click conversion rate stays click-through only. NO ROWS means no attributed conversion was recorded, not that data is missing — say exactly that, and check that an event setting exists (list_openai_ads_conversion_events) and that its pixel snippet is actually live on the site. RECEIVED EVENTS: pass recentEvents:true (no ids needed) to read a recent SAMPLE of the events OpenAI actually received on the pixel — type, event time, receive time, API channel and the event id — which answers "did OpenAI get the signup at all?" when a conversion is missing. It is a sample, not a complete log, and receiving an event is not the same as attributing it. BEYOND THE GOAL (on by default): each row and the reply carry attributedEvents, every event OpenAI attributed whether or not it is the campaign’s goal (e.g. purchases on a sign-up campaign), with count and value; totals.orderSales is attributed purchase value. An event count already includes goal activity: NEVER add it to conversions; a null value is unknown, not zero. A current day is not final until OpenAI’s daily processing runs. Read-only, free.

openai_ads_hotel_report

ChatGPT
Delivery per HOTEL PROPERTY for a ChatGPT Ads Hotel feed: impressions, clicks, spend, CTR, CPC and CPM per property (hotel id, name, brand, city, star rating), optionally per day or month and broken down by campaign, ad group or ad; timeseries:true returns daily totals across the selected properties instead. Filter by campaignIds / adGroupIds / adIds, exact properties [{feedId, hotelId}], or filters on hotel_name / hotel_brand / hotel_category / hotel_city / hotel_region / hotel_country_code (IN) and impressions / clicks / spend / ctr / cpc / cpm (GREATER_THAN / LESS_THAN). OpenAI’s rules, said in every reply: impressions and clicks mix settled and estimated data, spend and cost rates are settled only, and the coverage block decides whether an empty or short answer is real. No conversions and no attribution windows exist here. Hotel Insights is an OpenAI limited beta for accounts enrolled in Hotel reporting; others are refused by OpenAI. Read-only, 0 credits.

openai_ads_report

ChatGPT
Performance for ChatGPT Ads — impressions, clicks, spend, CTR, CPC, CPM, and CONVERSIONS with CPA, post-click conversion rate and attributed order sales / ROAS, in the ad account currency. OpenAI returns conversions only with granularity none or daily and with no segment or a country/device segment (never platform or product), and CPA, conversion rate and sales only with no segment at all; the report adds every column OpenAI allows for the shape you asked and its note names any it left out, so a missing column is their limit, not a zero. Since OpenAI’s 2026-09-30 change these conversions count click-through PLUS view-through goal conversions within the reporting windows (default 30-day click, 1-day view, dated by the ad interaction); CPA uses them and post-click conversion rate uses click-through only. clickWindowDays (7, 14, 30), viewWindowDays (0 = clicks only, or 1) and attributionTimeBasis choose those windows for every conversion column and never change the campaign; use the same ones as Ads Manager when comparing, and the reply’s attribution names the windows used. The scope follows the id you pass: none = the whole ad account, or campaignId / adGroupId / adId. PRODUCT-FEED CAMPAIGNS serving in the multi-product CAROUSEL unit also report per-card numbers: ask for them in fields — carousel_product_card_impressions, carousel_product_card_clicks, product_impressions, product_clicks, product_spend, product_ctr, product_cpc, product_cpm plus product_title / product_price / product_feed_id and the other product_* fields (complete from 2026-08-20 on a rolling 30-day basis); they come back under each row’s fields. A card impression counts when a product card becomes viewable and is NOT a billable impression, so never add it to spend math. granularity is hourly, daily, monthly or none (default daily); the default window is the last 30 days; segment by country or device for a breakdown, and level rolls the rows up by campaign / ad group / ad. A report with NO rows genuinely means there was NO delivery in that window — say exactly that; never present zeros as measured performance. attributedEvents:true adds every event OpenAI attributed beyond the campaign goal (purchases on a sign-up campaign) for the same scope and days; never add those counts to conversions. Read-only and free, so run it FIRST after connecting: it proves the key works with zero spend risk.

pin_telegram_message

ChatGPT
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.

pinterest_ads_report

ChatGPT
Performance for a Pinterest ad account — spend, impressions, clicks, CTR, effective CPC and conversions — at ANY of Pinterest’s four levels: the whole ACCOUNT, by CAMPAIGN, by AD GROUP, or by individual AD. Ad level is how you answer "WHICH AD IS WINNING". EVERY SUB-ACCOUNT LEVEL NEEDS ITS IDS — this was measured, not read: Pinterest refuses campaign level without campaignIds, ad-group level without adGroupIds, AND ad level without adIds ("Either ads id filter or both pin id and campaign id filters must be specified"), so there is NO account-wide per-ad call. Get the ids from list_pinterest_ads_campaigns (name a campaignId and it returns that campaign’s ad groups and ads), or use level:"account" for a whole-account total with no ids at all. At AD level Pinterest publishes one alternative its own refusal names: pinIds AND campaignIds TOGETHER, which reports every ad promoting those Pins — half of that pair is refused here naming the missing half. The level is inferred from whichever ids you pass, so naming campaignIds still reports by campaign. Default columns lead with that level’s OWN id and name, because a report whose rows cannot be told apart answers nothing. Window via since/until (YYYY-MM-DD) and granularity. Pinterest keeps only 90 days and refuses ranges longer than 90 days (at HOUR granularity: 8 days back, 3-day windows) — this refuses those up front with the reason rather than letting Pinterest return an opaque error. A report with ZERO rows genuinely means nothing delivered in that window; say exactly that and never present zeros as measured performance. Read-only, free.

pinterest_analytics

ChatGPT
ORGANIC Pinterest performance — impressions, saves, Pin clicks, outbound clicks and their rates, for the whole ACCOUNT, for the TOP PINS, for the TOP VIDEO PINS (with view-through and average watch time), or for ONE Pin. This is unpaid reach; pinterest_ads_report covers paid. Use scope:"top_pins" to answer "what is actually working on our Pinterest" — it ranks the account’s own Pins by whichever metric you sort on. NOTE Pinterest keeps only 90 DAYS of organic analytics and refuses a longer window, which is refused here with the reason rather than as an opaque error. A VIDEO Pin takes a different metric set from a static one (pass video:true for scope:"pin"). THERE IS NO BOARD ANALYTICS: Pinterest’s v5 API publishes no such endpoint, so board-level performance genuinely does not exist in any API — do not promise it. An unknown metric is refused by name, and a metric Pinterest omits from a row is MISSING data ("if a column has no value, it may not be returned"), never a measured zero. Works on Pinterest’s Trial access tier — unlike creating Pins, every read row in Pinterest’s access-tier table is available on Trial. Read-only, 0 credits.

pinterest_audience_insights

ChatGPT
WHO the Pinterest audience IS, rather than what it did — interest categories each carrying an affinity INDEX, plus demographics (ages, countries, devices, genders, metros). Three audiences: YOUR_TOTAL_AUDIENCE, YOUR_ENGAGED_AUDIENCE, and PINTEREST_TOTAL_AUDIENCE as the baseline to compare the other two against. This is an input to a creative brief, not a performance report. SAY THIS WHEN REPORTING: an affinity index is how much MORE likely this audience is to engage with a category than Pinterest’s baseline — it is a comparison, never a count — and when Pinterest flags size_is_upper_bound the audience size is an upper bound, not a measurement. There is no date range: Pinterest returns its current snapshot and names the date it is for. Read-only, 0 credits.

pinterest_targeting_analytics

ChatGPT
WHICH AUDIENCE SEGMENT actually delivered on Pinterest — ad performance broken down by keyword, targeted interest, age bucket, gender, location, region, country, placement, app type, media type and more. targetingTypes is REQUIRED because it is what the report breaks down BY. scope:"account" covers the whole ad account; "campaign" / "adGroup" / "ad" each REQUIRE their own id list, because Pinterest publishes no all-of-them form at those levels — that is Pinterest’s shape, not a limitation here. 90 days back in windows of at most 90 days, refused locally with the reason. An unknown targeting type is refused BY NAME; note Pinterest’s four per-level enums differ slightly, so a value valid at one level can still be refused at another. Read-only, 0 credits.

plan_ad

ChatGPT
Creative director: turn a brand + product/brief into a finished ad CONCEPT — copy variants (headline/primary/cta) plus an image_concept.prompt OR a video_storyboard, with the resolved recipe + the model ids to render with. Renders nothing; chain its output into generate_image / generate_video. THE USER’S EXPLICIT LENGTH IS SOVEREIGN: when they name a duration ("a 30 second ad", "make it 45s"), pass it as durationSeconds — the board is then AUTHORED to that length (its scenes sum to it) and render_ad renders it as one clip or stitched acts accordingly. Leaving it out lets the planner pick its own default, which is how an explicit ask silently becomes a 15s spot. Spends credits.

plan_variations

ChatGPT
Fan a brief into N DISTINCT ad angles (different hooks/mechanics/audiences), each with its own headline + visual brief — then render each with generate_image and rank with score_ad. LLM planning only; renders nothing itself. To plan AND render a batch of finished static ads for one or several products in one call (up to 20 per product), use make_static_ads.

post_edit

ChatGPT
MECHANICAL post-production on an EXISTING video (URL): ordered primitives run by ffmpeg in seconds, ~2 credits flat, NO AI model, as a NEW video. Ops: a branded end card (adds its seconds), trim, speed (0.5-2x), mute (whole or a window), audio_gain (-20..+6 dB), fade_out, music (a bed UNDER the clip, its own sound kept and ducked under: omit track = a free library track picked by mood, no attribution; or track = an audio link: an upload_file URL, a find_sound link, a video post's sound; track 'generate' composes it, paid, ONLY when the user asks; start/end, db, replace), voiceover (text spoken OVER it, its sound ducked; ~2.5 words/s; voice female|male; +~1 credit), watermark (brand logo), grain (anti-AI), text (timed words in a native look: style 'tiktok-classic' default / 'clean-minimal' / 'note-style' or a textStyle; start/end), join (this video FOLLOWED BY clips[]: Library URLs, direct files or public post links, as one 1080x1920 video, loudness matched). Presets are shortcuts: text/watermark take any x/y, grain any amount, join any ffmpeg transition, a bridge any sound link. 'A viral hook, then our clip' = videoUrl: the hook's post link + [{op:'join', clips:[{url: ours}], bridge}], and it ALWAYS gets a bridge unless the user asks for a bare cut: {kind:'impact'} cuts the hook just before its payoff (found from the footage; cutAt overrides) and lands our clip on a punch-in and flash, with the payoff sound FROM THE HOOK ITSELF: its own audio carries across the cut, else one generated from its frames (up to 8 credits), else a neutral impact. {kind:'text', text, then?} only when the user asks for words over the cut. No voiceover bridge: have our clip's host say the connecting line. matchCut = where our clip starts. ANY OTHER EDIT, op or bridge (whip, zoom, freeze, wipe, split screen, generated shot) is edit_timeline: free keyframes, any size. NEVER generate_video/render_ad for these.

post_performance

ChatGPT
Aggregate this brand's published posts to answer WHICH HOOKS AND SUBJECTS WORK. Groups by hook (default), subject, format (recipe), channel, media format or posting hour, reports the engagement RATE within each channel, and ranks the best and worst POSTS in each channel. Describe a post by the creative it carried (what it shows, its format, its link), not by its caption — the caption is the least important part of a post. THREE THINGS IT DELIBERATELY WILL NOT DO, and you should repeat them rather than paper over them: (1) it never sums metrics across channels — a LinkedIn impression and a TikTok view are different units, so every comparison is within one channel; (2) it SUPPRESSES a verdict below 5 measured posts and says so, because a confident recommendation from 3 posts is worse than none; (3) a post with no recorded hook (published outside Hermoso, or backfilled without a creation match) counts toward channel and format totals but never votes on which hook works. Present the finding verbatim if there is one, and the reason if there is not. Read-only, 0 credits.

post_to_bluesky

ChatGPT
Use this when the user wants to post to Bluesky. Publishing costs no credits. Publish a post to Bluesky as the connected account. Text up to 300 characters — Bluesky ALSO caps a post at 3000 UTF-8 bytes, so an emoji-heavy post can be under 300 characters and still be refused; Hermoso checks both before spending the round trip and says which limit and by how much. MEDIA: either up to 4 images (imageUrls + altText) OR one MP4 video (videoUrl + videoAlt), never both — a Bluesky post record carries a single embed and images and video are two different embed types. Video is MP4 only, up to 300MB at Bluesky's end (Hermoso can fetch up to 150MB from a URL), with optional WebVTT caption tracks; the aspect ratio is measured from the file. Bluesky requires a CONFIRMED EMAIL on the account before it will process any video — if it is unconfirmed you get a refusal saying so, and reconnecting will not help. Links in the text are made clickable automatically. LINK CARDS: Bluesky does NOT scrape links, so a URL posted bare renders as plain blue text — the client composing the post has to build the card. Hermoso builds one AUTOMATICALLY when the post has a URL and NO media: it fetches the page, uses its title/description and uploads its image as the card thumbnail. Pass linkCard:false to suppress it, or linkCard:{uri,title,description,thumbUrl} to control it (give both title and description and the page is not fetched at all). A POST CARRIES ONE EMBED, so a card and images/video cannot both ride: if you pass linkCard explicitly ALONGSIDE media the call is REFUSED by name rather than silently dropping one, and if the URL was merely in the text the MEDIA WINS and the reply says the card was skipped (the link stays clickable either way). Returns the post's public bsky.app URL. Connect at Settings > Connectors > Bluesky.

post_to_google_business

ChatGPT
Publish a Post to the brand’s Google Business Profile — the panel that appears on Google Search and Maps for the business. Text, optionally ONE PHOTO, and a call-to-action button. Google’s Posts API accepts NO VIDEO, so pass a still image. This PUBLISHES immediately and publicly on the business listing — show the user the exact text, photo and button and get an explicit yes BEFORE calling. If the account manages several listings, call list_business_locations first and pass locationId. EVENT and OFFER posts both REQUIRE a title and a start date (Google’s rule). On an OFFER, Google IGNORES the button’s link — pass redeemOnlineUrl instead. A CALL button dials the number on the listing and takes no link. Needs Google Business Profile connected (Settings > Connectors > Google Business Profile).

post_to_linkedin

ChatGPT
Use this when the user wants to post to their personal LinkedIn profile. Publishing costs no credits. Publish a post to the user’s connected LinkedIn profile — text, and optionally an image (pass its served URL as imageUrl). The image does NOT have to be something Hermoso generated: LinkedIn is served the bytes from us, so the URL must be Hermoso-hosted, and upload_file turns ANY file the user already has into exactly that. This PUBLISHES immediately and PUBLICLY — ALWAYS show the user the exact text and get an explicit yes BEFORE calling. Needs a connected LinkedIn account (Settings > Connectors > LinkedIn).

post_to_linkedin_page

ChatGPT
Use this when the user wants to post to a LinkedIn company Page. Publishing costs no credits. Publish a post to one of the user’s LinkedIn COMPANY PAGES — text, plus optionally an image, a video, a 2–20 image CAROUSEL (LinkedIn calls it a MultiImage post; pass the slides in order as imageUrls[]), or a LINK POST with a real preview card (linkUrl). USE linkUrl WHENEVER THE POINT OF THE POST IS A LINK: LinkedIn disables URL scraping for API partners, so a url sitting in the text renders as plain text with no card, and the card’s title, description and image only exist if you pass linkTitle / linkDescription / linkThumbnailUrl — read them off the page and supply them. The media need not be a Hermoso render — it must be Hermoso-HOSTED because we upload the bytes to LinkedIn ourselves, and upload_file turns ANY file the user already has into such a URL. ORGANIC CAROUSELS ARE COMPANY-PAGE ONLY — a personal profile cannot publish one and is refused by name, so send a deck here rather than to post_to_linkedin. This is a DIFFERENT thing from post_to_linkedin, which publishes to the person’s own profile: pick the one the user actually asked for and never substitute. organizationId comes from list_linkedin_pages; omit it only when the account administers exactly one Page. This PUBLISHES immediately and PUBLICLY — ALWAYS show the user the exact text and get an explicit yes BEFORE calling. A VIDEO POST CAN CARRY CAPTIONS AND ITS OWN COVER, and both are attached only during the upload: pass captionsSrt (SubRip content — LinkedIn is watched with the sound off) and videoThumbnailUrl (otherwise LinkedIn picks a frame for you). LinkedIn does NOT allow the image, video, captions or thumbnail of a published post to be swapped afterwards, so get all of that right first (the copy can still be edited with manage_linkedin_post).

post_to_meta

ChatGPT
Use this when the user wants to post now to Facebook, Instagram or Threads. Publish to a connected Facebook Page, its linked Instagram, OR the brand’s Threads account — text/link/image/VIDEO/CAROUSEL. A MULTI-SLIDE creative is a CAROUSEL, not several posts: pass the slides in order as imageUrls[] and they publish as ONE swipeable post (Instagram album, Threads carousel, Facebook multi-photo post). Never publish slide 1 of a deck on its own — the creative tells the viewer to swipe. target:"facebook" (default) posts to the Page; target:"instagram" publishes a photo or Reel to the linked IG business account (needs an image or video); target:"threads" posts to the connected Threads account (text, image, or video). Works with ANY media — a finished Hermoso ad OR an arbitrary user file: imageUrl/videoUrl accept a public https URL, a data: URI, or a Hermoso /generated path; for a LOCAL file (e.g. on the user’s desktop) call upload_file first and pass the url it returns. INSTAGRAM COLLAB: pass collaborators (up to 3 usernames) to invite other accounts to CO-AUTHOR the post — it then shows on their profile too once they accept. This PUBLISHES immediately — confirm the copy + media with the user first. Needs a connected Meta account (Settings > Connectors > Meta) with posting permission; Threads needs its own connection.

post_to_pinterest

ChatGPT
Use this when the user wants to publish a Pin to Pinterest. Publishing costs no credits. Create a Pin on one of the user’s Pinterest boards from any finished visual they have — image, video, or a 2–5 slide CAROUSEL (pass the slides in order as imageUrls[] and Pinterest publishes one swipeable Pin). Title, description and destination link ride along. The link is what makes a Pin drive traffic, so ask for it rather than omitting it, and the description is the text Pinterest search actually reads. boardId is REQUIRED: call list_pinterest_boards first and let the user choose. This PUBLISHES to their public profile — confirm the board, title and link before calling. Video Pins take a minute or two while Pinterest ingests the file. Needs Pinterest connected (Settings > Connectors > Pinterest).

post_to_telegram

ChatGPT
Use this when the user wants to post to a Telegram channel or group. Publishing costs no credits. Publish to a Telegram channel, group or chat as the brand's own bot. WHICH CHAT IS ALWAYS REQUIRED AND IS NEVER GUESSED: pass chatId as the public channel's @username (e.g. @hermosoai) or its numeric id (a group is negative; a supergroup or channel starts with -100). There is no default and there cannot be one — the Telegram Bot API publishes no method that lists the chats a bot belongs to, so Hermoso genuinely cannot know them. list_telegram_chats reports the chats that have MESSAGED the bot in the last 24 hours, which is a shortcut for finding an id and is NOT a roster: a chat missing from it can still be posted to. TEXT: up to 4096 characters on a text-only message, but only 1024 the moment ANY photo or video is attached — a caption is not a message, and Hermoso refuses the over-long one before spending the round trip and says which budget applied. MEDIA: one image (imageUrl), one video (videoUrl), or an ALBUM of 2–10 (imageUrls, in order) in which photos and videos may be MIXED — pass a videoUrl alongside imageUrls and it joins the album as one more item. Hermoso uploads the bytes rather than handing Telegram a link, which is what buys the larger ceilings: 10MB per photo and 50MB per video, where a link would be 5MB and 20MB. THE BOT MUST BE IN THE CHAT — an administrator with Post Messages for a channel, an unrestricted member for a group; if it is not, Telegram refuses and the error says to add it rather than to reconnect. Returns the message id and, for a PUBLIC chat, its t.me link — a private group has no public web link, so url comes back null rather than as a link that would 404 for whoever you hand it to. 0 credits. Connect at Settings > Connectors > Telegram.

post_to_tiktok

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

post_to_x

ChatGPT
Use this when the user wants to post to X (Twitter) now. Publish to the user’s connected X (Twitter) account — a single post, a post with an image or video render attached, a reply to an existing post, or a whole THREAD (pass thread as an array and each part is posted as a reply to the one before). This PUBLISHES immediately and PUBLICLY — ALWAYS show the user the exact text and get an explicit yes BEFORE calling. Can also run a POLL (2-4 options) instead of media, and restrict who may reply. LENGTH IS THE POSTING ACCOUNT’S, NOT A FLAT 280: 280 characters on an ordinary account, but UP TO 25,000 if that account has X Premium. Hermoso reads the account’s own subscription from X and sends the post rather than refusing something it may be entitled to; if X declines it on length, the reply says so and names the subscription X reported. Over 25,000 is refused for free — that is X’s own ceiling on every account. Text is NEVER truncated. AND A LONG POST IS CHEAPER THAN A THREAD: it is ONE billed X call where the same words split across five posts are five, so prefer one long post over threading when the account has Premium. Only the first ~280 characters show in the timeline; the rest sits behind “Show more”. Write altText whenever you attach a render. COSTS CREDITS: X charges per API request, so every post in a thread is billed, and a post containing a LINK costs roughly 13× one without — mention the cost before publishing a long thread. Each profile also has a rolling 24-hour ceiling on what it can spend at X, and a request that would cross it is refused WHOLE before anything publishes, so a batch of posts is bounded rather than open-ended. THIS TOOL POSTS ORGANICALLY — it does not create an ad campaign. X ads are built with create_x_ads_campaign -> create_x_ads_line_item -> create_x_ads_promoted_tweet, and a promoted post needs a post id, so publish here first and promote that post. Needs X connected (Settings > Connectors > X).

post_to_youtube

ChatGPT
Use this when the user wants to upload a video or Short to YouTube. Publishing costs no credits. Publish a finished video to the brand’s connected YouTube channel. Pass a Hermoso render URL (or an upload_file url for a local/external file). PUBLISHES PUBLICLY BY DEFAULT: a plain "post this to YouTube" puts it ON the channel (confirm the title with the user, as for any publish) and notifies subscribers as YouTube does. Pass the privacy the user states instead: "unlisted" (link-only) or "private" (eyes-only). A video meant to run as a YouTube/Google AD should go up "unlisted" — private videos CANNOT be used as ads. SCHEDULE it with publishAt, FILE it under the right categoryId (the default 22 "People & Blogs" is wrong for most ads), SUBSCRIBER NOTIFICATIONS FOLLOW PRIVACY — a public publish announces the video to the channel’s subscribers (YouTube’s own default), while unlisted/private uploads stay quiet; pass notifySubscribers explicitly to override either way. THUMBNAIL: pass thumbnailUrl, or a frame of the video is set for free; a thumbnail YouTube refuses never fails the upload, and thumbnailNote says why. YOUTUBE MUSIC: YouTube’s API takes no music track. Only when the user wants a track from YouTube’s own Audio Library: BEFORE uploading, tell them plainly it will go up UNLISTED (not on their channel, nobody sees it) so they can add the track in YouTube Studio on desktop (Content > the video > Editor > Audio), and that THEY must then switch it to Public there themselves; get their yes, then pass privacy:"unlisted". Never choose unlisted for music on your own. Needs a connected YouTube channel (Settings > Connectors > YouTube).

post_x_article

ChatGPT
Publish a long-form ARTICLE to the user’s connected X (Twitter) account — X’s own long-form format, which is a different thing from a long POST. Give it a title and a body written in MARKDOWN (or plain prose) and Hermoso converts it into the DraftJS content_state structure X requires: headings, paragraphs, bulleted and numbered lists, blockquotes, bold / italic / strikethrough, links, horizontal rules, fenced code blocks and pipe tables all carry across. FORMATTING IS NEVER SILENTLY DROPPED — anything X Articles cannot represent (inline code, an inline image) REFUSES the article for free and names exactly what and why, and allowLossy: true is the explicit way to publish it as plain text anyway. THE HARD LIMIT TO PLAN AROUND: X allows only about 10 Article DRAFTS and 5 Article PUBLISHES per account per DAY, it publishes that cap nowhere, and it counts a FAILED attempt against them — so never iterate on an article by republishing it, and use publish: false to save a draft for the user to read in X’s own composer when they want to review before it goes out. This PUBLISHES immediately and PUBLICLY: show the user the full text and get an explicit yes first, because a published Article can NEVER be edited (X’s own rule — edit_x_post will refuse it) and the only remedy is to delete and republish, which costs another of the five. An Article appears in the timeline as a title card, not as body text; readers open it. Costs credits (X bills per API request, and this is three of them). Needs X connected (Settings > Connectors > X).

posthog_insight

ChatGPT
Read back an insight the user already BUILT in their PostHog UI — funnels, retention curves, trends and paths. Call with NO id to LIST the saved insights with their names and ids; call with insightId for that insight's definition and computed result. THIS IS THE RIGHT WAY TO ANSWER A FUNNEL OR RETENTION QUESTION when the report already exists, and the reason is a documentation fact rather than a preference: PostHog's typed query kinds (FunnelsQuery, RetentionQuery, PathsQuery) are undocumented — their own docs say those "are mostly used to power PostHog internally and are not useful for you" and publish no request payload, no field table and no example for any of them, so building on them would be a private API that can change without notice. The two supported routes are HogQL (posthog_query) and a saved insight (this). Read-only, 0 credits.

preview_meta_ad

ChatGPT
Render a REAL preview of a Meta ad, per placement — Meta returns a link that shows exactly what a person scrolling Facebook or Instagram would see. Pass adAccountId + adId (from list_meta_ads), or creativeId. Optional placements (comma-separated): facebook_feed, facebook_feed_desktop, facebook_story, facebook_reels, facebook_profile_feed, facebook_marketplace, facebook_right_column, facebook_video_feed, instagram_feed, instagram_story, instagram_reels, instagram_explore, instagram_profile_feed, messenger_inbox, messenger_story, audience_network — default facebook_feed + instagram_feed + instagram_story + instagram_reels. Short words are read too and the reply says how: feed means facebook_feed, story/stories means facebook_story, reels means facebook_reels, instagram/ig means instagram_feed, ig_story, ig_reels. Free, read-only, spends nothing. THE LINKS EXPIRE AFTER 24 HOURS — always say so when handing them to a user. Use it straight after create_meta_ad, and whenever someone wants to approve an ad before it runs.

preview_openai_ads_ad

ChatGPT
Render a real preview of an existing ChatGPT Ads ad, so a human can LOOK at what will run instead of reading a list of ids back. Returns a hosted preview URL. THE LINK EXPIRES 24 HOURS AFTER IT IS CREATED — state that whenever you hand it to anyone, and generate a fresh one rather than re-sending an old one, because a dead link given to a client is worse than no link. Changes nothing about the ad; free.

preview_tag_manager_publish

ChatGPT
WHAT WOULD HAPPEN IF YOU PUBLISHED — free, read-only, and it publishes nothing. Returns the container, the version live on the site right now and what it holds, the exact list of staged changes (added, updated, deleted) as Tag Manager itself reports them, any merge conflicts, and the one sentence to show a user before asking them to agree. Use it to answer 'what is waiting to go live?' without going anywhere near the publish call. publish_tag_manager_container without confirm returns the same facts, so this is for when you want them with no chance of a publish at all. 0 credits. Needs Google Tag Manager connected.

product_sizzle

ChatGPT
Render an 18-30s music-led PRODUCT SIZZLE: ONE 15s Seedance 2.0 hero clip of the product, diced into fast cuts and intercut with typeset spec/CTA cards on a brand-coloured grain background, mixed to a music bed. Faceless by design — no people, no voiceover, no spoken lines; the cards carry every word, so nothing is left to a video model's spelling. Pass a real packshot as refImage or the label will not be yours. EXPENSIVE — the hero clip is the only paid leg and it is a full 15s Seedance render: ≈1,040 credits at the DEFAULT 1080p, ≈470 at 720p, ≈220 at 480p, ≈4,130 at 4k (call hermoso_capabilities for the live seedance-2 per-duration numbers; the dicing and the cards are free, and the music bed is already included in the quoted figure). Confirm the spend with the user before calling. For a talking/UGC ad use render_ad or generate_avatar; for a cheap deterministic format use make_template_ad.

publish_openai_ads_business_agent

ChatGPT
Publish a Business Agent’s draft so every campaign referencing it serves this version to real users. confirm:true required; show the user the instructions first.

publish_openai_ads_lead_form

ChatGPT
Publish the current draft of a ChatGPT Ads lead form so Business Agents can use it. expectedDraftRevisionId is the draftRevisionId you last read. Show the user the fields before publishing: the form is what real people will see.

publish_tag_manager_container

ChatGPT
PUBLISH THE CONTAINER — the ONE call in Hermoso that changes what runs on the customer's live website for every visitor, immediately. Everything else in Tag Manager is staged; this is not. TWO STEPS UNDER THE HOOD, because Tag Manager publishes a VERSION and not a workspace: Hermoso turns the draft workspace into a version and then publishes that version. CALLING IT WITHOUT confirm PUBLISHES NOTHING and is the correct first move: it returns what is live right now, how many tags, triggers and variables that live version holds, and exactly what would change — read back from Google, not echoed from your request. Show the user that, get an unambiguous yes, then call again. THE GATE SCALES WITH THE DAMAGE. A container that has NEVER been published has nothing that currently works and nothing this can break, so confirm:true alone is enough. A container that ALREADY has a live version is replacing something that works, so it additionally needs confirmPublicId set to that container's GTM-XXXXXXX public id — the id printed on the snippet installed on the website. It cannot be guessed; read it from list_tag_manager_containers or from the preview. IT REFUSES, FOR FREE AND WITHOUT PUBLISHING, in four cases you should relay rather than retry: merge conflicts in the workspace (somebody changed the same thing in the container — resolve in Tag Manager first), a workspace with no changes at all, a version Google reports a COMPILER ERROR in, and a container whose public id could not be read. TO UNDO A PUBLISH, publish the previous version again: pass versionPath (from list_tag_manager_versions) and no workspace is involved at all. The reply names the version that was live before this call in rolledBackTo for exactly that purpose, so a publish is reversible. READ verified BEFORE YOU TELL ANYONE IT IS LIVE. It is three-valued: true means the container's live version was re-read and IS the one just published; false means Google accepted it but something else is live, which can be Tag Manager's own read lag and must be reported as unconfirmed; null means the read-back could not run, so it is not known — never report false or null as published. TWO THINGS THAT SURPRISE PEOPLE: creating the version DELETES the draft workspace it was made from (Tag Manager's own behaviour) and opens a fresh empty one, whose id is in the reply, so any workspace id you were holding is stale afterwards. And publishing needs the PUBLISH right on the container in Tag Manager, Admin, User Management, which is separate from Edit — an account that can stage a tag can still be refused here, and that is a permission on the container, not a broken connection. 0 credits. Needs Google Tag Manager connected.

publish_to_shopify_product

ChatGPT
Attach a finished image to one of the merchant's Shopify product listings, as product media. Pass productId (from list_shopify_products — the gid://shopify/Product/… form) and a PUBLIC https imageUrl, which is what every Hermoso render returns. Shopify fetches the image server-side and processes it asynchronously, so the media can come back status PROCESSING and appear on the listing a moment later — that is success, not a failure. Only works for accounts created by installing Hermoso from the Shopify App Store. Free — the render was already paid for.

pull_competitor_ads

ChatGPT
Pull one named brand’s live ads from the Meta (Facebook and Instagram) Ad Library: deduplicated, sorted, with the brand’s own page resolved. One call, back in a few seconds. Use it when the user names a brand and asks what ads it is running ("show me the ads <brand> is running"). Covers the Meta Ad Library only; for a broader search across brands or platforms use research_ads. Spends credits.

query_openai_ads_feed_products

ChatGPT
List the products in a feed that match filters ({field, operator in|not_in|gt|gte|lt|lte|contains|not_contains|starts_with, values[]}) — the SAME filter shape an ad group’s productSet takes, so this previews exactly which products that ad group would advertise. Each product carries its price and filter values. It does not carry a per-product review status: that is only in ChatGPT Ads Manager > Products. Paginate with after. Read-only, 0 credits.

react_to_bluesky_dm

ChatGPT
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.

read_bluesky_dm

ChatGPT
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.

read_doc

ChatGPT
Read the text of a Google Doc Hermoso can reach — one it created, or one the user handed over with the Google file picker in the app (that is how an EXISTING doc becomes readable; find its id with list_drive_files). Pass documentId (from create_doc) OR paste a Google Docs URL as docUrl. Under the drive.file scope it reaches nothing else in the user’s Drive; if Google answers that the file was not found, the user has not picked it yet — ask them to pick it in the app rather than retrying. Returns the plain text of EVERY tab (each headed by its tab name when there are several), with smart chips (dropdowns, people, dates, rich links) shown as the text Docs displays, plus dropdowns[] — each chip with its title, current value and options, which is what update_doc dropdowns sets. Read-only, free.

read_intercom_conversation

ChatGPT
One Intercom conversation in full: every message in order (customer, teammate, bot, internal note) as plain text, plus its state, assignee and tags. Read it before drafting a reply. Read-only, free. Needs Intercom connected (Settings > Connectors > Intercom).

read_meta_conversation

ChatGPT
The messages in one DM thread, oldest to newest, each marked inbound or outbound — plus WHETHER A REPLY IS ALLOWED RIGHT NOW. Meta only permits a reply within 24 hours of the person acting, and the window is computed from their LAST MESSAGE rather than from the thread’s updated time (which includes our own replies and would report the window open for a day after WE spoke). Read this before drafting: "can I answer this?" is the question a person actually has, and finding out by being refused is the worst way to learn it. It also hands back replyTo, the id a reply goes to, so nobody has to guess which participant is the customer. ON INSTAGRAM Meta serves only the 20 most recent messages — a truncated thread is unreachable history, not a short conversation, and the reply says so. Read-only, 0 credits.

read_meta_leads

ChatGPT
Read the LEADS a form (formId) or a single ad (adId) has collected — the real answers people submitted, fetched live from Meta. THIS RETURNS REAL PEOPLE’S CONTACT DETAILS (names, emails, phone numbers). Treat it as the user’s own customer data: show only what they asked for, never post a lead list anywhere public, and do not copy it into an unrelated document. Hermoso PULLS these on demand and keeps no copy — nothing here watches, polls or files leads anywhere, so if the user wants this batch kept, put it somewhere THEY own in the same turn (a Google Sheet with create_sheet / append_to_sheet, a doc, or their own CRM). Meta makes each lead available for 90 days after it is submitted; anything older is handled in Meta’s own Leads Center and CRM integrations. Narrow with since/until (ISO dates), page with cursor, or pass redact:true to see counts, timestamps and which ad produced each lead WITHOUT the personal details. NEEDS leads_retrieval — see list_meta_lead_forms for what to do if Meta refuses it.

read_sheet

ChatGPT
Read cells from a Google Sheet Hermoso can reach — one it created, or one the user handed over with the Google file picker in the app (that is how an EXISTING spreadsheet becomes readable; find its id with list_drive_files). Pass the spreadsheetId (from create_sheet) OR paste a Google Sheets URL as sheetUrl. If Google answers that the file was not found, the user has not picked it yet — ask them to pick it in the app rather than retrying. Returns a 2-D array of values.

read_x_broadcast_chat

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

recast_motion

ChatGPT
Motion transfer: re-perform a clip's motion, camera and timing with YOUR character, product, clothes or place; sound kept. image (+ images, @Image1, @Image2… in prompt) = who and what. engine auto (default), a person clip recast with people: one photo (talking, dance or full-body) or an AI person made from words, ≤15s -> wan (on the beat, keeps set and framing, follows every blink and mouth shape); several photos (one per person) or >15s -> h3 (H3 Max Recast: ≤4 photos, one per person, 5-30s, no shot over 15s, keeps set, light, cuts; engine h3 picks it by name); otherwise seedance (Seedance 2.5, ≤9 pictures, 4-30s, keeps set and beats; a person clip only if our render or faceRoute, else wan/kling). kling = Kling Motion Control (ONE picture, its background becomes the set, 3-30s); wan = Wan 3.0, recommended (≤9 pictures, keeps the set, ≤15s); wan-prime = Wan 3.0 Prime by name (costs more). voice/language: the new performer gets a new voice (same timing) or speaks another language (lip-synced), in the same job and price. Billed per output second, ~5 min per 5s; dryRun quotes.

reddit_ads_bid_suggestion

ChatGPT
Reddit’s own suggested bid for a given objective, bid type and targeting — a median, a sensible range, and the hard floor below which Reddit will not accept a bid. Amounts come back in the ad account’s currency. Free, creates nothing. Use it to pick bidAmount for create_reddit_ads_ad_group instead of guessing a number that either never wins an auction or overpays.

reddit_ads_forecast

ChatGPT
Ask Reddit how big a targeting set is and roughly what a budget would buy — total reachable audience, the targeted slice, and estimated impressions, clicks and reach. Free, creates nothing, spends nothing, so run it BEFORE building an ad group to sanity-check targeting that may be far too narrow or far too broad. Budget is an ordinary amount in the ad account’s currency. These are Reddit’s estimates, not a guarantee — say so when reporting them.

reddit_ads_history

ChatGPT
Read the CHANGELOG for a Reddit ad account — what was changed, from what to what, by which member, and when. This is the tool for "performance fell off a cliff on Tuesday, what changed?" and for auditing what an agent or a teammate actually did. Call it with nothing but the ad account to get every change; narrow it with a date window, change types (BUDGET, BID, STATUS, TARGETING…) or specific campaign / ad group / ad ids. An empty result genuinely means nothing was changed in that window — say that, do not read it as missing data. Read-only, free.

reddit_ads_report

ChatGPT
Performance for a Reddit ad account — impressions, clicks, spend, CTR, CPC, eCPM, reach and any of Reddit’s ~450 metric fields, optionally broken down by campaign, ad group, ad, date, hour, community, country, gender, interest, keyword, placement and more. Money comes back in WHOLE UNITS of the ad account’s currency (Reddit reports micro-currency; the conversion is done for you) — and that currency is not necessarily dollars, so check the account. Reddit only accepts HOURLY window boundaries; plain YYYY-MM-DD dates are accepted here and snapped for you. A report with ZERO rows genuinely means nothing delivered in that window — say exactly that and never present zeros as measured performance. ONE DATED LIMIT WORTH PLANNING AROUND: from 2026-10-30 Reddit caps the HOUR breakdown at windows of 7 DAYS OR FEWER and answers 400 beyond that. Hermoso warns before that date and refuses after it rather than relaying a bare vendor 400 — for a longer stretch, run one report per <=7-day window, or use the DATE breakdown for a daily series over the whole period. Read-only, free.

reframe_video

ChatGPT
Reframe a video to a different aspect ratio (e.g. 16:9 master -> 9:16 vertical) with smart subject tracking. Paid render; returns the served URL of the reframed video.

refund_stripe_charge

ChatGPT
Refund a Stripe charge (ch_… or a pi_… payment intent): the full refundable amount, or a partial amount in major units; optional reason duplicate / fraudulent / requested_by_customer. ALWAYS needs confirm:true, test keys included. Read back. Needs Stripe connected (Settings > Connectors > Stripe).

register_merchant_developer

ChatGPT
ONE-TIME SETUP, and the FIRST thing to run when Merchant Center calls are being refused. Google blocks every Merchant API call until Hermoso's Google Cloud project is registered against the merchant's account, and says so verbatim ("GCP project ... is not registered with the merchant account"). This performs that link. It is NOT a broken connection and reconnecting cannot fix it. Google asks for up to 5 minutes afterwards before the API starts answering.

reject_x_chat_admin_request

ChatGPT
Use this when a request to become admin of a group the brand admins should be turned down. Needs X connected; X bills per call, so it costs credits.

reject_x_chat_join_requests

ChatGPT
Use this when people waiting to join a group the brand admins should be turned away. Approving a join is done in the X app. Needs X connected; X bills per call, so it costs credits.

reject_youtube_comment

ChatGPT
Use this when a comment should be hidden as unfit for display. Reversible with approve_youtube_comment. banAuthor:true also auto-rejects every future comment from that author on the channel. Free.

remake_video_for_brand

ChatGPT
Remake a video you like FOR THIS BRAND from its link — a TikTok, Instagram Reel, Facebook video or reel, X post, YouTube Short or video, or a direct video file URL. Hermoso WATCHES it first (frames across the whole clip plus a transcript of the voiceover, on-screen text and cut map), then plans a storyboard that keeps its hook device, structure, jump cuts and pacing while swapping in THIS brand's product, cast, setting and words — never the original's words, face or brand. The new ad MATCHES THE ORIGINAL'S LENGTH (capped at 60s) unless durationSeconds is given. Renders nothing: pass the returned creative to render_ad to make the video. Costs the plan plus about 2 credits to read the link. The reply says exactly what was watched, and when a platform will not hand over the footage (YouTube sometimes refuses servers) it says the plan rests on the captions and thumbnail only. For a local file, upload_file it first and pass the URL.

remember

ChatGPT
Save a durable fact or PREFERENCE about the brand, audience, or the user’s creative TASTE (e.g. “audience is first-time homebuyers”, “prefers bold lime accents”, “always captions off”) into the workspace Memory so it shapes FUTURE ads. For lasting things, not one-off requests. Merges into the existing Memory (never overwrites); de-dupes on identical text. NEVER how Hermoso, a tool, a connector or a platform API behaves (what a call returns, errors, permissions, limits, ids) and never a phone number or email — those are refused.

remix_static

ChatGPT
The OLD NAME of clone_static, kept so agents that already call it keep working. It is the same tool with the same inputs, result and cost; prefer clone_static.

remove_applovin_ads_creative_set

ChatGPT
Take an AppLovin creative set out of its campaigns (all of them, or campaignIds). AppLovin’s API has no delete or archive, so this is its only removal: the set and its assets stay on the account and can be added back. Call it WITHOUT confirm FIRST: nothing changes and you get the campaigns it is in; show the user, then call with confirm:true, plus confirmName (its exact name) and confirmChildren when the reply asks for them. Needs the Campaign Management key, which exists once AppLovin enables API access on the account; without it the answer says how to get it.

remove_bing_webmaster_site

ChatGPT
Remove a site from the connected Bing Webmaster account — the heaviest thing in this connector. GATED ON BLAST RADIUS: called WITHOUT confirm it removes nothing and reports whether Bing holds the site as VERIFIED and how many sitemaps are registered on it; a site that is verified or has any sitemaps then ALSO needs confirmSiteUrl set to that exact site string, which is a fact you only have after inspecting it. A site that was never verified and has no sitemaps is the empty radius and stays a one-call removal — friction that does not scale with the loss just gets routed around. RE-ADDING A VERIFIED SITE LATER MEANS PROVING OWNERSHIP FROM SCRATCH; no API restores it. Nothing about the site itself or its Bing ranking changes. The site is also dropped from this brand's shared list, and if the read-back that confirms the removal cannot run the answer says "could not tell", never "removed". 0 credits.

remove_bing_webmaster_sitemap

ChatGPT
Remove a sitemap from Bing. GATED ON BLAST RADIUS, not just on intent: called WITHOUT confirm it removes nothing and reports what Bing says that sitemap actually carries — its URL count, status and type — and a sitemap carrying any URLs then ALSO needs confirmUrlCount set to that number. confirm:true proves you meant to remove something; the echoed count proves you aimed at the sitemap you inspected rather than one that merely shares a name. A sitemap Bing reports as empty stays a one-call removal. Removing it does NOT remove those pages from Bing's index — Bing simply stops using it to find new ones — and it can be submitted again at any time. If the read-back that confirms the removal cannot run, the answer says "could not tell", never "removed". 0 credits.

remove_member

ChatGPT
Remove a member from this profile by email — they lose access (you can re-invite them later), AND every connection THEY made on this profile is disconnected with them: their own X, LinkedIn, TikTok, YouTube, Pinterest, Threads… connections (on a channel holding several accounts, only the accounts they added), and any extra login or authorization they added. Connections the owner or anyone else made are never touched, and one with no record of who connected it is treated as the owner's. Queued posts that would publish through their accounts, or that they scheduled themselves, will not go out. The unconfirmed call reports exactly which connections and how many scheduled posts — relay that to the user, then call with confirm:true.

remove_openai_ads_daily_spend_limit

ChatGPT
Remove the account-wide DAILY spending limit on ChatGPT Ads. This takes a spending CEILING off the whole account, so campaigns may then spend up to their own budgets every day; date-range spend-limit windows are not touched. Like every account spending-limit request it needs postpaid INVOICE billing and billing-management permission; a card-billed account is refused whatever key is used. Without confirm:true AND expectedRevision (the revision list_openai_ads_spend_windows returns) nothing changes and the reply names the limit that would be removed. With several daily rules, pass windowId to name the one to remove (OpenAI requires it then). The result is read back from ChatGPT Ads.

remove_x_broadcast_chat_message

ChatGPT
Use this when one message in the brand’s live broadcast chat must be removed. Cannot be undone; needs confirm:true. Get messageId from read_x_broadcast_chat. Needs X connected; X bills per call, so it costs credits.

remove_x_broadcast_chat_moderator

ChatGPT
Use this when someone should stop moderating the brand’s broadcast chats. Pass userId or username. Needs X connected; X bills per call, so it costs credits.

remove_x_chat_admins

ChatGPT
Use this when people should stop being admins of a group the brand admins. userIds are numeric X account ids. Needs X connected; X bills per call, so it costs credits.

remove_x_chat_members

ChatGPT
Use this when people should be removed from a group the brand admins. Needs confirm:true. userIds are numeric X account ids. Needs X connected; X bills per call, so it costs credits.

remove_youtube_playlist_item

ChatGPT
Use this when one entry should come out of a playlist. Takes the entry’s itemId (from list_youtube_playlists with a playlistId), not the videoId. The video itself is not deleted. Free.

remove_youtube_watermark

ChatGPT
Use this when the branding watermark should come off every video on the connected YouTube channel. Free.

rename_reddit_ads_asset

ChatGPT
Change a stored Reddit creative asset’s name — the ONLY field Reddit lets you edit on an asset (its PATCH takes {name} and nothing else). The reply is read back from the library. Free.

render_ad

ChatGPT
Use this when the user asks for a video ad, Reel, TikTok ad, UGC video or commercial for their business or product. It renders a finished ad on a real video model, on their brand (real product photos, brand voice), as an mp4 ready to post: timed shots, clean speech, text set in post, no music, captions or end card unless asked. Pass plan_ad’s full output as creative (plan_ad turns a one-line ask into the concept). UGC and cinematic plans render from a painted STORYBOARD (a cinematic spot paints a location anchor first, and a hero when someone is on camera); a product-only spot renders from the real product photo, and board:true or foundations:'choose' adds an identity sheet, a moodboard and a board. A plan that fits one clip of the render model renders as one continuous pass; a longer one as STITCHED ACTS (the fewest balanced clips), never time-compressed. Most models cap a clip at 15s and the longest at 30s: dryRun:true shows the act split free, before spending. CAST A SAVED CREATOR with creator (list_creators) so the same person stars in every ad; otherwise each render casts a new face. The user’s own pictures (creative.inserts, or inserts) are laid in full screen at their seconds. 1–3 min; poll get_job while it renders. Spends credits.

reply_to_google_business_review

ChatGPT
Answer a customer review publicly, as the business, on the brand’s Google Business Profile listing — or delete a reply that is already there. THIS IS AN UPSERT: a listing has exactly one reply per review, so replying to a review that already has an answer REPLACES it rather than adding a second. Google only accepts replies on a VERIFIED listing. Deleting is public and immediate, so it is confirm-gated. Write the reply in the brand’s voice and answer the specific complaint — a generic reply under a one-star review is worse than none. Needs Google Business Profile connected and the project approved.

reply_to_inbox_item

ChatGPT
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 two DM sources (meta_dm, bluesky_dm) — 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.

reply_to_intercom_conversation

ChatGPT
Send a reply to the CUSTOMER on an Intercom conversation, from the brand's inbox as the teammate who owns the connection. This reaches a real customer and cannot be unsent. Read the conversation first (read_intercom_conversation), draft the reply in the brand's voice, show the user the exact words, and send only when they ask: call with confirm:true. Without confirm it sends nothing and returns the preview. The result is read back from the conversation Intercom returns. Plain text; blank lines become paragraphs. Never leave a placeholder such as [Your name] or [Order number] in it: Intercom already shows the customer which teammate replied, so sign with the brand's name or leave it unsigned, and ask the user for any fact you do not have. Free. Needs Intercom connected (Settings > Connectors > Intercom).

reply_to_linkedin_comment

ChatGPT
Speak AS your LinkedIn company Page: comment on one of its posts, or reply to somebody else's comment on it. Community Management surface — same separate connection as list_linkedin_comments. organizationId is WHO IS SPEAKING (the Page, from list_linkedin_pages) and postUrn is WHAT IS BEING COMMENTED ON; they are different things and both are required. To reply to a particular comment rather than the post itself, pass that comment's commentUrn from list_linkedin_comments as parentComment. LinkedIn's API does not support images in comments. It also throttles comment creation per member, and a throttle is reported as a throttle so you wait rather than retrying into it.

reply_to_meta_comment

ChatGPT
Post a public reply to a comment on the brand’s Facebook or Instagram post. This is PUBLIC and posted as the brand — show the user the exact wording and get their go-ahead first.

reply_to_meta_message

ChatGPT
Send a text reply to someone who has messaged the brand. THIS REACHES A REAL PERSON — show the user the exact text and who it goes to, get a yes, then send. Pass conversationId as well as recipientId and Hermoso checks Meta’s 24-hour window for free BEFORE sending, and refuses with the real reason instead of letting Meta refuse it; without one it sends and discloses that the window could not be checked. HERMOSO SENDS REPLIES ONLY: messaging_type is always RESPONSE, and proactive messages and message tags are not offered at all — that is a deliberate product boundary, not a gap. THE ONE PROACTIVE THING MESSENGER ALLOWS IS A MARKETING-MESSAGES SUBSCRIPTION: to ask a person in the inbox to SUBSCRIBE to marketing messages, call request_messenger_optin (Meta’s own opt-in template, in the ads group — enable_tools(["ads"]) or find_tools if it is not in your list). Never answer that with a plain-text reply asking them to say YES, and never say the capability does not exist. And a closed window is a RULE that only the person writing again reopens. recipientId is a PAGE-SCOPED ID on Messenger and an INSTAGRAM-SCOPED ID on Instagram (read_meta_conversation returns it as replyTo); a username or a handle is not one and cannot be turned into one. ACCEPTED IS NOT DELIVERED — never report it as delivered or read. 0 credits.

reply_to_thread

ChatGPT
Post a reply to a Threads post — the brand’s own or someone else’s. This PUBLISHES publicly under the brand’s account, so show the user the exact wording and get their go-ahead first.

reply_to_tiktok_ads_comment

ChatGPT
Post a public reply, as the brand, under a comment on one of your TikTok ads — the TikTok twin of reply_to_meta_comment and reply_to_youtube_comment. THE REPLY IS LIVE IMMEDIATELY AND IS PUBLIC, posted as the TikTok identity you name, so let the user approve the wording. TikTok requires FIVE ids and every one of them is on the comment row itself — pass back commentId, adId, tiktokItemId, identityId and identityType exactly as list_tiktok_ads_comments reported them rather than assembling them by hand; a mismatched identity is a public mistake. identityType on the comment endpoints is narrower than on the ad builder: only TT_USER or CUSTOMIZED_USER. The reply comes back as TikTok’s own stored row with its new comment id, so the answer is TikTok’s record and not an echo — and because it is your own comment, delete_tiktok_ads_comment can remove it.

reply_to_tiktok_comment

ChatGPT
Reply to an existing comment on a TikTok post the authorized account owns — TikTok's twin of reply_to_meta_comment and reply_to_youtube_comment. Text (≤1,200 characters) or an image; at least one is required. A REPLY IS THE ONE PLACE TIKTOK ACCEPTS A PLAIN IMAGE URL (pass imageUrl); an uploaded imageUri + width + height also works. TikTok silently hides replies it flags as spam, so read it back with list_tiktok_comment_replies if visibility matters. NEEDS THE TIKTOK ACCOUNT AUTHORIZATION (see tiktok_account_status).

reply_to_youtube_comment

ChatGPT
Post a public reply to a comment on the connected channel, as the channel. This is PUBLIC and immediate — show the user the exact reply text and get an explicit yes before calling. Get commentId from list_youtube_comments. Needs a connected YouTube channel.

report_bug

ChatGPT
Report a bug in Hermoso to the team. Use this when something in Hermoso genuinely misbehaves — a tool errors unexpectedly, returns a wrong or malformed result, a render comes back broken, or documented behaviour doesn't match what happened. Include what you were trying to do, the exact tool call and arguments, and what came back. Do NOT use it for out-of-credits, a policy refusal, or a missing capability (use request_feature for that). Free, no credits.

report_youtube_comment_spam

ChatGPT
Use this when a comment is spam. It is a REPORT to YouTube, not a removal, so do not tell the user the comment is gone. Free.

repost_thread

ChatGPT
Repost an existing Threads post to the brand’s own Threads profile — the Threads equivalent of a retweet. It is how a brand amplifies a customer’s post, a mention, or one of its own older threads without copying the text, and there was previously no way to do it. Works on any Threads post id: list_threads_posts, list_threads_mentions and search_threads_keyword all return them. This creates a NEW post on the profile, so show the user what is being reposted and get a yes first. Threads publishes NO un-repost endpoint — because a repost returns its own media id, deleting THAT id with delete_thread is the likely undo, but Meta does not document it, so check the profile afterwards rather than promising it worked. 0 credits. Needs Threads connected.

request_feature

ChatGPT
Ask the Hermoso team for a capability that doesn't exist yet. Use this when you need something Hermoso genuinely can't do — an unsupported platform or channel, a missing model, an export format, a tool that would have completed the user's task but isn't available. Say what the user was trying to achieve, not just the feature name — the use case is what gets built. Free, no credits.

request_messenger_optin

ChatGPT
Send Meta’s opt-in request (the notification_messages template) to someone ALREADY in a Messenger conversation with the Page, inside the 24-hour window. If they accept, Meta posts a messaging_optin webhook with their subscription token and they appear in list_messenger_subscribers. One identical-title request per person per week. Free to send. The PSID comes from the Page inbox.

request_tiktok_tto_spark_authorization

ChatGPT
Ask the creator of a campaign video for Spark Ads authorization, so the brand can run their organic post as an ad. THIS IS THE ONLY WAY TO OBTAIN A SPARK ADS CODE WITHOUT THE CREATOR PASTING ONE OUT OF THE TIKTOK APP BY HAND: once they accept, get_tiktok_tto_spark_authorization hands back the code that authorize_tiktok_ads_spark_post takes, which then makes the post usable by create_tiktok_ads_ad. THE NUMBER OF DAYS IS A REQUEST, NOT A SETTING: TikTok says the creator picks the actual window when approving, so read the granted dates back rather than assuming what was asked for. action "EXTEND" lengthens an authorization the creator has already approved. TikTok returns no confirmation body for this call, so the state in the reply was read back separately rather than echoed. Free.

reschedule_post

ChatGPT
Change a post that is still QUEUED — move it to a different time, rewrite the caption, swap the media, add or drop a channel, or change which board / Page / company Page / listing it goes to. PASS ONLY WHAT CHANGES: an omitted field is left exactly as it was, and an explicit empty string CLEARS one (linkedinOrganizationId:"" moves a company-Page post back to the person’s own profile). The edited item is re-checked against the identical rules its create passed — visibility the channel can honour, per-channel length, media the channel can carry — so an edit can never slip past a refusal that a create would have caught. Get the id from list_scheduled. Something that already went out cannot be changed: a published post is edited or removed with edit_facebook_post / delete_meta_post / set_facebook_video_cover / manage_linkedin_post / delete_x_post, not rescheduled.

research_ads

ChatGPT
Use this when the user wants to see the ads a brand or competitor is running, or ad examples for a market. Open-ended ad research that needs JUDGMENT across platforms — comparisons, "what angle is working", "who else is doing this", anything where the right sources are not known up front. It is an agentic loop (several rounds of library pulls plus a written synthesis) and typically takes 30-60 seconds, so it is the WRONG tool for a question that names its own answer. For one named brand’s live ads use pull_competitor_ads; for one keyword or one advertiser on Meta use search_meta_ads — both are a single call and return in a few seconds. Spends credits — an agentic loop, so a handful rather than the one-call cost of a targeted search.

resize_ad

ChatGPT
Re-lay out ONE finished static ad for other placements: the same ad, product, copy (word for word), logo and style, recomposed natively for each canvas rather than cropped. Pass image and optionally aspectRatios from 1:1, 4:5, 9:16, 16:9, 3:4, 4:3 (default 1:1, 4:5 and 9:16; the ad's own ratio is skipped). Reads the ad's text first (3 credits) so every line survives, then one image edit per canvas. When the saved brand has a product photo and the ad shows that product, the photo rides with the edit and the product's label is read and checked against it: re-printed from the photo only when it came out wrong (the check alone is charged when it is right; one small extra charge finds the product first). fixLabel:false turns that off. Priced before it runs. For VIDEO use reframe_video.

respond_instagram_collab_invite

ChatGPT
Answer a collab invite from list_instagram_collab_invites. accept:true makes this account a co-author and the creator’s post appears on its profile; accept:false declines. accept is REQUIRED — never guess which way the user wants it. The reply is read back from Instagram (the invite leaves the pending list), not taken from the vendor’s 200. Free. Instagram allows 50 answers per account per day.

restyle_video

ChatGPT
RESTYLE a clip into a new LOOK: every frame redrawn, shots, motion, camera, framing, timing and sound kept. Looks: Restyle looks in hermoso_capabilities (claymation, knitted yarn, cel-shaded CG anime…), your own words, or a look BUILT FROM YOUR IMAGES (styleImages); characters draws your own characters in. engine auto (default) picks by what is in the clip: a real-looking person -> kling (Kling O3 Edit, 3-15s); none -> wan (Wan 3.0, recommended, ≤15s) else seedance (Seedance 2.5, 4-30s); wan-prime = Wan 3.0 Prime by name (costs more). Paid; dryRun quotes it. One change only: edit_video.

retry_scheduled

ChatGPT
Send a post that FAILED again. A scheduled post fans out across its channels INDEPENDENTLY, so a failure is usually PARTIAL — LinkedIn 401s while Instagram published fine — and this re-fires ONLY the channels that did not succeed by default (list_scheduled reports them as retryable). It re-queues the same content as a NEW post that goes out RIGHT AWAY — the queue picks it up on its next pass, within seconds — and the original keeps its failure record so the history still shows what went wrong. Naming a channel that already published is REFUSED rather than quietly posting a second time. Two independent belts stop a double-post: a channel that genuinely published can only REPLAY (nothing is posted), and a channel whose outcome is UNRESOLVED — the platform timed out and may be holding the post — refuses with that reason instead of guessing. Retry after fixing the cause — and you can fix it IN THIS CALL: pass boardId, pageId, linkedinOrganizationId, locationId, message or captions to correct the value that failed, and the corrected post is re-validated exactly like a fresh schedule. That matters because the commonest cause is a field, not an outage: a Pin aimed at the wrong board fails identically however many times it is re-sent. Anything you do not name is copied from the original. To send the same thing again ON PURPOSE, use duplicate_scheduled.

run_post_refill

ChatGPT
Run AUTOPILOT POSTING (autoposting) NOW instead of waiting for its daily turn, or REVIEW the drafts it made — "approve them all", "delete that one", "change the caption", "too salesy, remember that". PREVIEW BY DEFAULT: it returns the posts it WOULD make — each new picture’s scene, the caption, the channels and the price — rendering nothing and queueing nothing. The preview still WRITES the scenes and copy with a model, which bills a few credits (the reply says how many). dryRun:false actually renders a fresh image or video for each post within the budget (credits are spent) and then, by mode, schedules them ("auto") or keeps them as drafts ("review"). It never re-posts the Library. REVIEWING A BATCH: pass approve (draft ids, or ["all"]) to schedule drafts, discard to delete them, edit ([{id, message?, captions?, title?, at?, channels?}]) to change one first (a new at reschedules it); get_post_refill lists them. learn saves notes future batches follow (free). redo remakes ONE draft from a note: PAID, so without confirm:true it returns only the price; confirm only on the user’s yes. A scheduled post can still be pulled before it goes out with cancel_scheduled. Every caption is screened against the voice rules and a failing one is dropped, so a plan can come back shorter than the cadence — the reason is in the notes.

save_creator

ChatGPT
Add a portrait to this workspace’s reusable CAST so the SAME person can star in future ads — the headless twin of the app’s + > Pick a creator > save. Pass the portrait’s public url (a generate_image render of an AI person, or a photo of a real person you have permission to use, or of yourself; never a photo just because it is public) plus a name to call them by; from then on list_creators returns them and their url can be re-passed to generate_avatar / generate_video / recast_motion. Saving is FREE and renders nothing. LIKENESS: leave source "generated" for an AI-made person (free on every plan) and use "upload"/"social" for a REAL person. Using a real person’s face, likeness or voice confirms you are them or have their consent, take full, unlimited responsibility for its use and will cover any claim against Hermoso (hermoso.ai/terms). Paid plan only; recorded.

save_dm_automation

ChatGPT
Create (no id) or edit (id) an AUTO-DM / AUTO-REPLY automation: "when someone comments GUIDE on my posts, DM them my guide link", "reply to anyone who DMs PRICE with our pricing page", "reply to people who mention us on X". PAUSE one with {id, enabled:false}; PRACTICE MODE (dryRun:true) logs what it would send and sends nothing. Instagram/Facebook: a keyword comment (one post, the next post, any post; on Facebook also ads) gets a private reply in DMs/Messenger, a DM keyword or ig.me/m.me ref link gets a message, with optional link buttons and rotated public replies. X: a keyword @mention or a reply under your post gets a public reply, a DM keyword gets a DM (X allows an automated DM only after the person DMed you). Once per person per post; a DM saying STOP opts that person out. Instagram and Facebook are free; X is billed in credits for every mention, reply or DM X delivers to the account plus each reply or DM sent, and running out stops the X rules. It messages real people, so confirm the text first.

save_instagram_dm_automation

ChatGPT
Create (no id) or edit (id) an automation that DMs people automatically: a comment with a keyword on one post / the next post / any post gets a private reply, or a DM / story reply / ig.me link gets an answer. Optional link buttons, public comment replies, follow gate and dry run. Once per person per post; Meta allows private replies within 7 days of the comment. It DMs real people, so confirm the text first. Free, no AI on the send path.

save_pinterest_pin

ChatGPT
Save an existing Pin onto another of the account’s boards. This is the GENERALLY AVAILABLE way to get a Pin onto the right board — unlike update_pinterest_pin, which Pinterest keeps in a limited beta — so reach for it first when a Pin is on the wrong board. It COPIES rather than moves: Pinterest’s save endpoint creates a new Pin and the original stays where it is, so delete that one with delete_pinterest_pin if it should not be in two places. Let the USER pick the destination board (list_pinterest_boards) — a Pin on the wrong board is a public mistake. 0 credits. Needs Pinterest connected.

save_playbook

ChatGPT
Save a reusable PLAYBOOK — the strategy takeaways worth re-running: the hooks that work, the angles, the formats, and the concrete plays. Use it to keep what a competitor_teardown or mine_angles just found, or to bank a creative you want to repeat. Lands in the same Playbooks library the web app lists, runs and manages. Distinct from save_skill (a directive applied to every ad) and from the swipefile (raw saved creative). Free.

save_skill

ChatGPT
Save a reusable custom SKILL — a named creative directive/playbook applied to future ads (a hook formula, a UGC recipe, a compliance rule, a named specialist persona like “our founder-story style” or “short-form ad strategist”). Distill an imperative, self-contained directive. Merges into the workspace Skills library (list_skills shows built-ins + your custom skills).

save_to_drive

ChatGPT
Save a Hermoso render — or ANY file — into the user’s connected Google Drive. Pass a Hermoso render URL as url (or urls[] for several); for a local/external file, call upload_file first and pass the url it returns. Optional folder (created if new) + name. Returns the Drive file(s) with a webViewLink. Needs Google Drive connected (Settings > Connectors > Google Drive — one connection covers Drive, Sheets and Docs). NOTE: Hermoso uses the drive.file scope, so it reaches ONLY the files it created plus any the user explicitly handed over with the Google file picker in the app — never their whole Drive.

save_to_onedrive

ChatGPT
Save a Hermoso render — or ANY file — into the user’s connected Microsoft OneDrive. Pass a Hermoso render URL as url (or urls[] for several); for a local/external file, call upload_file first and pass the url it returns. Optional folder (created if new) + name. Returns the OneDrive file(s) with a webViewLink. Needs OneDrive connected (Settings > Connectors > OneDrive).

save_to_swipefile

ChatGPT
Save one or more ads/creatives to a named SWIPEFILE collection, creating the collection if it does not exist — the headless twin of the heart on every ad card in the web app. Use it whenever research turns up something worth keeping: a competitor ad from search_meta_ads / pull_competitor_ads, an organic post, or one of your own renders. Saved ads persist to the workspace board the web Swipefile tab shows, and feed the taste signal every future ad is planned against. De-dupes: re-saving the same ad (same key/link/media) MOVES it into the named collection instead of duplicating it. Free.

schedule_post

ChatGPT
Use this when the user wants to schedule a social media post for later, on one channel or several at once. Scheduling and publishing cost no credits on any Hermoso plan (X alone bills a few credits per call). Queue a post for a future time on one or more connected channels at once: facebook, instagram, threads, tiktok, youtube, linkedin, x, pinterest, bluesky, telegram — TEN channels, every one live. google_business is in the schema but HELD BACK (Google’s API allowlist) and is refused at enqueue. A MULTI-SLIDE creative goes in imageUrls[] as a CAROUSEL, in order — never schedule just its first slide. Name the time in at, or pass useQueue:true for the brand’s next free POSTING SLOT (what “just queue it” means). imageUrl/videoUrl take a Hermoso render URL or an upload_file URL. captions gives a channel its own wording; the rest use message. PINTEREST AND YOUTUBE SHOW A TITLE: title (max 100 chars), derived from the caption when omitted; YOUTUBE also takes description, tags, thumbnailUrl. Every PER-CHANNEL SETTING is a parameter below, carried straight to the real publisher — TikTok’s brandedContent / yourBrand disclosures (set them whenever the post is commercial), Google Business topicType / event / offer / actionType, an X thread / poll, Instagram collaborators, and the rest. Channels are attempted INDEPENDENTLY: one failing never blocks the others. SOME CHANNELS MUST BE TOLD WHICH ACCOUNT, and Hermoso never guesses: Pinterest needs boardId (list_pinterest_boards) or is refused; a LinkedIn COMPANY PAGE post needs linkedinOrganizationId (list_linkedin_pages) or it goes to the person’s own profile; several Facebook Pages need pageId (list_meta_pages), several Google Business listings locationId (list_business_locations) — resolve those FIRST and let the user pick, or the post is refused when it fires. A scheduled post GOES LIVE PUBLICLY by default on every channel, never quietly downgraded. Only if the user asks, set visibility (or visibilityByChannel): ‘unlisted’ (YouTube) · ‘private’ (YouTube, or TikTok SELF_ONLY) · ‘draft’ (TikTok, or an unpublished Facebook Page post). A visibility a channel cannot do is REFUSED now, never posted weaker later.

score_ad

ChatGPT
Virality/performance prediction for a finished ad (image or video URL): overall score, per-dimension breakdown (scroll-stop, hook, clarity, brand/product, CTA, retention, goal fit), strengths, and the single biggest fix. Use BEFORE spending on distribution, or to rank variants.

search_apple_ads_apps

ChatGPT
Find the App Store app to advertise and its adamId — the id every other Apple Ads tool needs (create_apple_ads_campaign’s appAdamId, a creative’s destination, an eligibility check). Call it with NO arguments to list the apps THIS Apple Ads organization owns, which is what a first-time advertiser wants: only an app you own can be promoted, so a public app you merely found by name cannot be used as promotedObjectId. Pass query to search the App Store catalogue by name or developer, or adamId to fetch one app’s full details — including availableStorefronts, the definitive list of countries the app can be advertised in and the set a campaign’s countries must be a subset of. Read-only, free.

search_apple_ads_geo

ChatGPT
Resolve a place NAME into the geo id an Apple Ads ad group actually targets. THIS IS THE ONLY WAY TO GET THOSE IDS: an ad group’s targeting takes adminAreaIds / localityIds / postalCodeIds / countryIds, it does NOT accept place names, and nothing else in Hermoso produces one — so without this tool those targeting fields cannot be used at all. (A campaign’s own countries field is a different thing and takes plain ISO country codes.) supplySource is REQUIRED and is not a formality: APPSTORE has NO PostalCode entity (postal codes are a Maps-level geo), and MAPS has NO Country entity and covers only the US and Canada — both are refused for free with the reason. Pass query with at least 2 characters, or "*" to list everything at an entity level. Rows flagged soft-blocked (low search volume, sparse coverage) are still targetable — Apple returns them by default with their eligibility attached. To go the other way and look UP ids you already have, pass geos:[{id|legacyId, entity}] instead of query. Read-only, free.

search_console_url_inspection

ChatGPT
THE LIVE INDEX STATUS OF ONE PAGE, straight from Google: the index verdict, coverage state, whether robots.txt allows it, which URL Google picked as canonical versus the one the page itself declares, when it was last crawled and as which crawler, plus rich-result and mobile-usability verdicts. This is the tool for "why is this page getting no traffic" — a page Google cannot crawl, or whose canonical Google has reassigned to a different URL, will never rank however good the copy is. THE URL MUST SIT INSIDE THE PROPERTY: a URL-prefix property covers only its own prefix, while an "sc-domain:" property covers every scheme and subdomain. Important: QUOTA — Google allows 2,000 inspections per DAY and 600 per minute PER SITE, and that is the CUSTOMER'S property quota, not ours: inspect the handful of URLs the question is actually about and NEVER loop this over a sitemap, a page list or a crawl. Read-only, 0 credits.

search_google_ads

ChatGPT
Structured Google Ads Transparency pull for ONE advertiser (by domain or advertiserId) — use when you know the brand; use research_ads for open-ended research. Deliberately fetches the cheap BASIC listing (get_ad_details=false, ~1 credit — the detailed variant with per-ad headlines costs 25 credits/call and is not exposed here). Returns compact JSON {advertiser, format, adUrl, image, firstShown, lastShown} per ad.

search_instagram

ChatGPT
Organic Instagram REELS keyword search (/v2/instagram/reels/search — our only IG keyword surface; profile/hashtag pulls go through instagram_profile and search_instagram_hashtag). Returns compact JSON {desc, author, handle, plays, likes, link, cover} per reel, ranked by plays. Spends about a credit.

search_instagram_audio

ChatGPT
Audio the brand may legally put under a Reel — music or original sound — with title, artist, length, whether it is eligible for ads, a preview link and a download link. Omit the query for what is TRENDING right now; pass one to search. Everything returned is audio Instagram has authorized for third-party use. Instagram offers this library only to an account linked to a Facebook Page and connected through Meta; an account connected through Instagram Login alone has none, and this says so. TO USE ONE, pass its id as audioId on post_to_meta or schedule_post (target instagram, a video): Instagram puts the track under the Reel itself. Download links expire after roughly 1.5 days. Read-only, free.

search_instagram_hashtag

ChatGPT
Read the PUBLIC Instagram posts carrying a hashtag — the caption, the media, the permalink and the like and comment counts — on the brand’s own Meta credentials rather than a scraper. This is category and competitor listening: find how a market actually talks about a product, then USE it — feed the captions into mine_angles, or write the brand’s next post or ad from the language you found. TWO EDGES AND THEY ANSWER DIFFERENT QUESTIONS: edge:"recent" (default) is what has been posted in the LAST 24 HOURS, which is Meta’s own hard limit, so a hashtag with millions of posts legitimately returns ZERO on a quiet day — never report that as "nobody is posting about this", ask again with edge:"top", which has no time limit. Only PUBLIC, non-promoted posts are ever returned, from public accounts only, and never in chronological order. A SHORT RESULT IS NOT A SMALL HASHTAG: Instagram limits a page by total response SIZE rather than by post count, so a tag whose posts are carousels returns fewer rows per page than a tag whose posts are single images — never compare two tags by how many rows came back. BUDGET: Instagram allows each Instagram account 30 UNIQUE hashtags in any rolling 7 days, so choose the tags deliberately; re-querying one already used in the window is free. Read-only, 0 credits.

search_instagram_shopping_products

ChatGPT
The products in one catalog that can actually be TAGGED on an Instagram post — this is where the product_id for a product tag comes from. Omit q to see everything tag-eligible; pass a product name or SKU to narrow it. THIS IS A SMALLER SET THAN THE CATALOG HOLDS: a product can be in the catalog, counted by list_meta_catalog_products, and still not be taggable, so an empty answer here is never evidence the catalog is empty. Meta only SHOWS a tag whose product review_status is "approved" — an unapproved one is accepted, stored and shown to nobody, so the reply flags them. Read-only, 0 credits. Whether Meta has approved Hermoso's app for the permission this needs is read LIVE from Meta: when it has not, list_connectors says so under Meta and this tool's refusal says Meta hasn't approved Hermoso for it yet. That is Meta's decision about Hermoso, not the user's connection, so never tell them to reconnect for it.

search_intercom_contacts

ChatGPT
Find people in the brand's Intercom: by exact email, or by part of a name or email (query). Returns id, role (user or lead), name, email, phone, created and last seen. Read-only, free. Needs Intercom connected (Settings > Connectors > Intercom).

search_linkedin_ads

ChatGPT
Structured LinkedIn Ad Library search by company name, keyword, or companyId — use for a targeted B2B pull; use research_ads for open-ended research. Returns compact JSON {advertiser, headline, description, cta, link, media, dates, impressions} per ad — LinkedIn's library includes impression counts. Spends about a credit.

search_linkedin_ads_targeting

ChatGPT
Look up LinkedIn TARGETING entities by name and get their URNs — locations, job titles, industries, seniorities, company sizes, skills, job functions, interests, employers, degrees, fields of study, member behaviours. LinkedIn’s targeting values are opaque URNs (urn:li:geo:103644278 is the United States) with no guessable form, so ALWAYS resolve an audience here before passing it to create_linkedin_ads_campaign, and NEVER invent a URN — a made-up one either 400s or, worse, targets somebody else. If nothing matches, say so plainly. Read-only, free.

search_linkedin_brand_partnerships

ChatGPT
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.

search_meta_ads

ChatGPT
Structured Meta (Facebook/Instagram) Ad Library pull — use when you know exactly WHAT to fetch: a keyword (query) OR one advertiser (companyName / pageId). Returns compact JSON {page_name, body, cta, link, dates, media} per ad. For open-ended research that needs judgment across platforms, use research_ads instead. Spends a credit or two.

search_microsoft_ads_profiles

ChatGPT
Find the LinkedIn profile ids Microsoft Advertising can target: a company (CompanyName), an Industry or a JobFunction. A company is found with Microsoft’s own company search (3+ characters); industries and job functions are matched against the profile list Microsoft publishes for them. Microsoft publishes no lookup for JobTitle or JobSeniority ids, so those cannot be searched here; an id you already have can still be targeted. Read-only, free. Pass the ids to set_microsoft_ads_profile_targeting.

search_pinterest_ads_targeting

ChatGPT
THE IDS AN AD GROUP’S targetingSpec NEEDS. create_pinterest_ads_campaign and create_pinterest_ads_ad_group accept interests, locales, locations, age buckets and keywords — this is where those values come from, and guessing one is the worst option available: Pinterest ACCEPTS a well-formed but wrong interest id and the campaign then quietly targets the wrong people. Pass targetingType (INTEREST, GEO, LOCATION, LOCALE, AGE_BUCKET, GENDER, APPTYPE, KEYWORD, AUDIENCE_INCLUDE, AUDIENCE_EXCLUDE) and optionally query to narrow the list. INTERESTS ARE A TREE: pass interestId instead to drill into one and get its children, which is how you get from "Food" to something specific enough to target. Pinterest publishes no search parameter of its own, so query filters the full list HERE — the note says so, because a filter we applied is not a filter the vendor applied. Read-only, 0 credits, no new permission (it uses ads:read, already granted).

search_posts

ChatGPT
The POSTS people make ABOUT a subject — a brand ("liquid death"), a product, a hobby ("coffee"), a hashtag ("#homecafe") — from whoever posted them, across organic TikTok, Instagram Reels and YouTube in ONE call, ranked by views. Not the brand's own ads (search_meta_ads / research_ads) and not the people (find_creators folds these same posts into creators): use it to see what is actually being posted and watched about a subject, to find clips worth cloning (remake_video_for_brand), and to read the hooks and angles an audience already responds to. About one credit per platform searched (one query each by default; queries adds "best X" / "X review" / #tag variants, each a paid call); repeats inside 20 minutes are free.

search_reddit

ChatGPT
Reddit keyword search (/v1/reddit/search, top-ranked) — a goldmine for the customer's OWN words (pain points, objections, language) to mine into ad hooks and copy. Returns compact JSON {desc (title+selftext), subreddit, upvotes, comments, link} per post. Spends about a credit.

search_reddit_ads_targeting

ChatGPT
Look up the exact values Reddit ad-group targeting expects, so none of them has to be guessed. kind:"communities" searches subreddits by keyword and returns each one’s NAME plus its subscriber count — targeting wants the bare name ("running"), NOT the t5_ id and NOT "r/running". kind:"geolocations" lists targetable places (pass country like US, or a city search) — targeting accepts a country code or one of the returned ids. kind:"interests" lists Reddit’s interest taxonomy — targeting wants the id ("pets_v3"). Read-only, free. Use this before create_reddit_ads_ad_group rather than inventing a community name.

search_snapchat_ads_targeting

ChatGPT
Resolve Snapchat targeting options to the ids an ad squad needs — countries (REQUIRED: an ad squad cannot be created without at least one), regions, metros, age groups, genders, languages, device OS or interests. NEVER invent one of these ids: invented targeting is silent and spends money on the wrong people. Region and metro lookups need a countryCode because Snapchat scopes those lists per country. Interests live in SEVERAL taxonomies at different paths (scls, vac, shp) which are not interchangeable, so name one rather than assuming. Read-only, free.

search_threads

ChatGPT
Organic Threads keyword search (/v1/threads/search) — short-form text/social posts for trend + voice research. Returns compact JSON {desc, author, handle, likes, link, cover} per post. Spends about a credit.

search_threads_keyword

ChatGPT
Search PUBLIC Threads posts for a keyword or topic — competitor listening, finding what people say about a product, or sourcing real customer language for ad copy. Distinct from search_threads, which reads a specific profile.

search_threads_locations

ChatGPT
Search Threads’ public place index by name (or by latitude+longitude) and get location ids. Use this when the brand has a PHYSICAL location — a restaurant, salon, gym, store — so the post can be geotagged to it. Pass the chosen id as post_to_meta(locationId) with target:"threads".

search_tiktok

ChatGPT
Organic TikTok keyword search (there is NO TikTok ad library) — top-performing videos to mine for hooks/trends/remixable creative. Returns compact JSON {desc, author, handle, plays, likes, link, cover} per video, ranked by plays. Use research_ads for open-ended research. Spends about a credit.

search_tiktok_ads_targeting

ChatGPT
Look up the exact ids TikTok ad-group targeting expects, so none of them has to be invented. kind:"location" resolves TikTok’s targetable regions — an ad group CANNOT be created without location ids, TikTok refuses it in its own words ("‘location_ids’ or ‘zipcode_ids’ must be specified"). kind:"interest" resolves the interest categories and kind:"interest_keyword" the additional interest KEYWORDS (both attach to an ad group). kind:"hashtag" resolves real targeting HASHTAGS — until 2026-08-11 this kind pointed at TikTok’s interest-keyword endpoint and quietly returned interest categories instead; note that hashtag ids feed TikTok’s actions[] field, which Hermoso does not send yet, so treat hashtag results as RESEARCH rather than targeting you can apply. kind:"language" the language codes. A made-up id either fails the create or, worse, targets somebody else and spends money silently, so always resolve here first and never guess. Read-only, free.

search_x

ChatGPT
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 this bills the ACTUAL count returned, never the number asked for). Needs X connected.

search_youtube

ChatGPT
Organic YouTube keyword search (/v1/youtube/search) — videos to mine for hooks/angles/long-form structure. Returns compact JSON {desc (title), author, handle, plays, link, cover} per video, ranked by views. Spends about a credit.

send_applovin_ads_events

ChatGPT
Send website conversion events to AppLovin from the server (its Conversion API): purchase, generate_lead, add_to_cart and the rest, up to 100 per batch, each with the visitor’s public IP, user agent and one identifier. Email and phone are hashed with AppLovin’s own normalisation before sending; a purchase dedupes on its transaction id. Needs the Conversion API key, which AppLovin enables per account on request; without it the answer says how to get it. Free.

send_bluesky_dm

ChatGPT
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.

send_linkedin_conversions

ChatGPT
Stream conversion events to LinkedIn (Conversions API) — server-side conversion tracking for things that happen where the Insight Tag cannot see them: a CRM deal closing, an offline sale, a phone order, a qualified lead. Needs a rule from create_linkedin_conversion_rule, and that rule needs campaigns associated or nothing is attributed. PASS THE PERSON’S PLAIN EMAIL ADDRESS as email — Hermoso applies LinkedIn’s own normalization and SHA-256 hashes it on the server, and the plaintext is never stored or logged. NEVER COMPUTE THE HASH YOURSELF: an invented digest is a well-formed 64-character string that matches nobody, and LinkedIn accepts it with a 201, so the failure is completely silent (emailSha256 exists only for a source system that already holds real digests). SEND EVERY IDENTIFIER YOU HAVE — email, liFatId (the li_fat_id click id LinkedIn appends to ad click URLs), ipAddress, firstName WITH lastName, an externalId, a lead URN — the match rate is what decides whether the conversion counts at all. Events must have happened in the past 90 DAYS. ACCEPTED IS NOT MATCHED: success means LinkedIn took the events, not that any matched a member, and no API reports the match rate — check attribution in linkedin_ads_report over the following days and never present acceptance as conversions. Up to 5000 events per call. Free.

send_messenger_marketing_message

ChatGPT
Send a PAID marketing message on Messenger to opted-in subscribers (Meta act_<AD>/messages). A campaign created in the last ~hour answers 2300012 then 2300041 (Meta still preparing it) with the seconds left; prefer an existing campaign from list_messenger_marketing_campaigns. One message per subscriber per 12 hours — Meta’s rule, enforced before dispatch and by Meta. message.type: text | button (text + up to 3 web_url buttons) | generic (a card: title, subtitle, image, tap-through url, up to 3 buttons) | media (imageUrl or videoId + buttons). Give subscriptionTokens (≤200 per call, from list_messenger_subscribers) OR customAudienceId of a MESSENGER_SUBSCRIBER_LIST audience of 100+ people for a bulk send. Meta bills the ad account per delivered message. A successful send means ACCEPTED, not delivered: read sent / delivered / read / clicked / failed per tracking id with get_messenger_marketing_message_status (Meta pushes them on the Page webhook; the Page must be subscribed with subscribe_meta_webhooks fields ["marketing_messages"]). dryRun:true previews the wire body and sends nothing. Meta’s frequency caps are silent: a refusal saying the person is capped is Meta protecting them, not a broken send.

send_microsoft_ads_offline_conversions

ChatGPT
Tell Microsoft Advertising about conversions that happened OFF the website — a phone sale, an in-store purchase, a lead that closed weeks after the click — so smart bidding on that account stops optimising against only what converted online. Each row needs conversionName (an existing OFFLINE conversion goal, matched by name — list_microsoft_ads_conversion_goals shows them), conversionTime (within the last 90 days AND after the click), and an identifier: either microsoftClickId (the msclkid Microsoft appends to the landing-page URL) or, for ENHANCED conversions, a plain email and/or phone. YOU PASS PLAIN VALUES AND THE HASHING HAPPENS SERVER-SIDE: emails are normalized and SHA-256’d to Microsoft’s own published five-step spec, which is verified against Microsoft’s own worked digests. A PHONE MUST ALREADY CARRY ITS COUNTRY CODE (+14255551234) and is refused otherwise — Microsoft’s spec says "normalize to E.164 format with country code" and stops there, and guessing which country a bare 4255551234 belongs to is not a mistake you would ever see: the upload succeeds and matches nobody. SAY THIS WHEN REPORTING: accepted is not the same as visible. Microsoft takes up to 6 hours to show offline conversion data and applies nothing at all in the first 2 hours after a goal is created, and a conversion only counts inside the goal’s own conversion window. Duplicates are silently ignored (the first wins), so re-sending a batch is safe and changes nothing. 0 credits.

send_reddit_ads_conversions

ChatGPT
Report conversions to Reddit server-side — purchases, leads, sign-ups, or your own custom events — so Reddit can attribute them to the ads that caused them and optimise delivery toward them. This is what makes a CONVERSIONS campaign work; without it Reddit optimises blind. Send events as close to real time as you can: Reddit REFUSES anything older than seven days, and deduplication against the browser pixel only works inside two days. Pass ordinary email addresses and phone numbers — they are canonicalised and SHA-256 hashed on our server before they reach Reddit, and a value you already hashed is passed through untouched. The more match keys per event (email, phone, clickId, uuid, externalId, IP + user agent) the better the attribution. Set conversionId on every event if you ALSO run the browser pixel, or the same purchase is counted twice. Costs no credits and spends no ad money — this is measurement. Needs the "adsconversions" permission: if Reddit answers 403, the connection predates it and the user must reconnect Reddit Ads.

send_tiktok_ads_events

ChatGPT
SERVER-SIDE CONVERSION TRACKING — report a purchase, signup, form submission or lead to TikTok straight from a server, so TikTok can attribute it and optimise delivery toward it. This is what makes a pixel useful: a pixel with no events is inert, and a conversion campaign optimising toward an event nobody sends has nothing to learn from. ALWAYS OFFER testEventCode FIRST: an event carrying it lands in the Test Events tab of Events Manager and is EXCLUDED from reporting, attribution and optimisation, whereas an event sent WITHOUT it is a real, permanent conversion that TikTok will optimise against and that NO endpoint deletes. The code is copied out of Events Manager in a browser (open the pixel or event set > Test Events > the code button under "Test Server Events") and no API mints one, exactly like a Spark Ads authorization code. eventTime is a Unix timestamp in SECONDS — milliseconds are refused here for free, because TikTok accepts them and reads them as a date tens of thousands of years away, so the event is silently attributed to nothing. Emails, phones and external ids are trimmed, lower-cased, E.164-normalized and SHA-256 hashed by Hermoso exactly as TikTok specifies before anything leaves the process; a value that is already a 64-character hash is passed through untouched, so nothing is ever double-hashed. A phone with no "+" country code is refused rather than guessed. Free.

send_tiktok_ads_offline_events

ChatGPT
REPORT REAL-WORLD CONVERSIONS — an in-store purchase, a phone booking, a signed contract — so TikTok can attribute them to the ads that caused them and optimise delivery toward them. This is what makes a TikTok campaign measurable for a business whose sale does not happen on a website. Emails and phone numbers are normalized and SHA-256 hashed by Hermoso exactly as TikTok specifies before anything leaves the process, using the SAME implementation as send_tiktok_ads_events; a value that is already a 64-character hash is passed through untouched, and a phone with no "+" country code is refused rather than guessed. EITHER emails OR phone_numbers is REQUIRED on every event — an offline conversion identifying nobody is attributed to nothing. Warning: THE TIMESTAMP IS AN ISO-8601 STRING HERE ("2026-08-19T19:11:01Z"), NOT the Unix-seconds NUMBER that send_tiktok_ads_events takes: TikTok accepts a wrong-shaped one, reads it as some other date and attributes the conversion to nothing, so it is refused here for free. Warning: THERE IS NO TEST MODE: TikTok documents test_event_code on Events API 2.0 and on NEITHER offline endpoint, so everything sent here is a real, permanent conversion that no endpoint deletes. To rehearse the pipeline first, send the same events through send_tiktok_ads_events with eventSource "offline", the same event set id and a testEventCode from Events Manager — those land in the Test Events tab and are excluded from reporting, attribution and optimisation. Warning: REPORTING NEEDS A ROLE, NOT JUST A CONNECTION: TikTok requires the connected user to be an ADMIN or OPERATOR of that advertiser account, measured live — the same token can create and delete Offline Event SETS and still be refused on the events themselves. If that happens Hermoso says so plainly rather than telling anyone to reconnect, because reconnecting cannot grant a role; it has to change in TikTok Business Center. Free.

send_whatsapp_broadcast

ChatGPT
Send ONE approved WhatsApp template to MANY recipients — a customer list, a segment, a launch announcement. TEMPLATES ONLY: WhatsApp accepts free-form text only inside the 24-hour customer-service window that opens when THAT person messages the business, so a free-form broadcast is a separate bet on a separate window per person and Meta refuses each closed one. META HAS NO BULK ENDPOINT, so this is a paced fan-out — one message per recipient, about 10 a second — which is why it runs as a QUEUED JOB and returns a job id rather than a result: nothing has been sent when the call returns. Poll it by calling this tool again with jobId (or use get_job), and read the PER-RECIPIENT outcome; a partial failure is reported as one, never as success. PER-RECIPIENT VARIABLES ARE THE POINT: pass recipients as objects { to, components } so each person gets their own {{1}}, {{2}} values in a single broadcast. Duplicates are removed (the same number twice is a second charge for one person and trips Meta’s 6-second per-recipient limit) and unusable numbers are REFUSED BY NAME rather than silently dropped — pass skipInvalid:true to send to the rest, and they are still named in the result. RUN IT WITH dryRun:true FIRST: that costs nothing, sends nothing, and reports the exact recipient count, the template’s real category, what Meta bills for it, and whether the list fits inside the business portfolio’s moving 24-hour messaging limit. Warning: META DOES NOT DELIVER MARKETING MESSAGES TO WHATSAPP USERS IN THE UNITED STATES AT ALL (error 131049, platform-wide since 1 April 2025) — the sends are still ACCEPTED and given message ids, so a US marketing broadcast looks like a success and is not one; the dry run says how many recipients are on +1. META BILLS THE BUSINESS, not Hermoso credits: per message delivered, at Meta’s own per-country rates, marketing always charged and utility/authentication free inside an open service window. WhatsApp policy REQUIRES the recipient’s opt-in first and nothing in the API enforces it — Meta punishes it afterwards through quality rating. Confirm-gated in code: show the user the message, the template and the recipient count, get an explicit yes, then call again with confirm:true AND confirmCount set to the exact number of distinct recipients. ACCEPTED IS NOT DELIVERED — never report a broadcast as delivered. 0 Hermoso credits.

send_whatsapp_message

ChatGPT
Send a WhatsApp message from one of the brand’s registered numbers to a customer. TWO KINDS, and which one is legal depends on time: pass template (an APPROVED template name) to START a conversation — the only thing WhatsApp accepts outside the 24-hour customer-service window — or text for a free-form reply INSIDE that window, which opens only when the customer messages the business first. Hermoso does not receive WhatsApp webhooks yet, so it CANNOT see whether that window is open: a free-form message is sent optimistically and Meta refuses it with a re-engagement error if it is closed, which the reply explains rather than reporting as a broken connection. THIS REACHES A REAL PERSON’S PHONE AND META BILLS THE BUSINESS for the conversation, so it is confirm-gated: show the user the exact message and the recipient, get an explicit yes, then call again with confirm:true. WhatsApp policy REQUIRES the business to hold the recipient’s opt-in first. An unapproved template is refused BEFORE the send, against the status Meta holds. ACCEPTED IS NOT DELIVERED — the reply reports what Meta accepted, and delivery arrives on a webhook we do not receive, so never tell the user it was delivered. THE VARIABLE COUNT IS CHECKED BEFORE ANYTHING IS DISPATCHED: Hermoso reads the template’s own components back from Meta and refuses a payload that supplies the wrong number of values, naming exactly what is missing — for free, before a real phone is involved, instead of Meta answering 132000 after the attempt. It refuses only what it can prove; anything it cannot determine is sent for Meta to judge. 0 Hermoso credits.

send_x_broadcast_chat

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

send_x_dm

ChatGPT
Send ONE direct message from the connected X account, either into an existing conversation (conversationId) or to a named account (participantId — the NUMERIC id, not an @handle; sending to someone with no existing thread starts one). THIS IS A PRIVATE MESSAGE TO A REAL PERSON AND IT CANNOT BE TAKEN BACK: show the user the exact wording and the exact recipient and get an explicit yes BEFORE calling. Never send the same message to several people — send one, to one named recipient. X may refuse a message to someone whose settings do not accept DMs from accounts they do not follow; that refusal comes from X, not from Hermoso. Costs credits per send. Needs X connected with direct-message access — an X account connected before DMs shipped must be reconnected once (Settings > Connectors > X).

set_apple_ads_status

ChatGPT
Pause or enable an Apple Ads campaign, ad group, keyword or ad. PAUSING is immediate and needs no confirmation. ENABLING IS THE ONE SWITCH THAT ARMS REAL MONEY: it requires confirm:true, and without it nothing changes and the refusal names the object Apple actually holds under that id — read back from Apple, never echoed from your input, because aiming at the wrong campaign is invisible until money moves. An object serves only when it AND every parent above it are ENABLED, so enabling a keyword inside a paused campaign spends nothing. Prefer pausing to deleting: pausing is reversible and Apple’s delete is not. A CREATIVE, AN ASSET AND A LOCATION GROUP ARE DELIBERATELY ABSENT from the levels here and that is not an oversight: none of them has an advertiser status — a creative and a location group carry only a read-only systemStatus Apple computes — so to stop a creative serving, pause the ADS that reference it, and to stop promoting a set of shops, pause the AD GROUP that targets the location group. The reply reports the status Apple STORED, which is a different claim from the one it accepted. Free.

set_applovin_ads_status

ChatGPT
LIVE or PAUSED for an AppLovin campaign (campaignId) or creative set (creativeSetId), read back. LIVE starts real ad spend: show the user the budget, get an explicit yes, then call with confirm:true. Pausing is always safe. AppLovin’s API has no delete; pause instead. Needs the Campaign Management key, which exists once AppLovin enables API access on the account; without it the answer says how to get it.

set_competitor_watch

ChatGPT
Set (or STOP) this workspace's standing COMPETITOR WATCH — the weekly job that re-checks each named brand's ad libraries and reports what is NEW since last time. The same watch the web app's Ad Spy > Watching tab manages, and the same one the weekly digest email is sent from (turn that email on/off with update_settings({watchEmail})). This REPLACES the whole watched list, it does not add to it — pass every brand you want watched, every time. Max 5 brands (the server trims past that). Pass an EMPTY list to stop the watch entirely, which also clears the findings. Give a domain wherever you know one: Google Ads Transparency is looked up BY DOMAIN and is skipped for a brand without one, and the domain is what resolves the right Meta page for a brand with an ambiguous name. The run itself spends credits against the ad libraries (roughly 3 per brand on Meta, 1 each on Google and LinkedIn) and is hard-capped per run server-side, so an oversized watch is trimmed rather than allowed to run away. Setting the list is free; only a run spends. runNow:true runs it once IMMEDIATELY (a background job — it spends now) and then keeps the weekly cadence; leave it off and the first check is a week out. The country and the platform mix are NOT settable here — a re-set inherits whatever the pending run already carried (US / Meta for a watch that has never been configured otherwise). Read the findings back with list_watch_findings.

set_connector_accounts

ChatGPT
Set WHICH of a connector's accounts this profile is allowed to post to and spend from — Facebook Pages / Instagram / Meta ad accounts, Google Ads customers, LinkedIn company Pages (and the personal profile), Pinterest or Microsoft Advertising ad accounts. Pass ids from list_connector_accounts. This REPLACES the current selection: anything you leave out is un-shared, and an EMPTY list shares nothing (publishing then refuses — it fails closed by design, and the server re-verifies every id against the live connection, so an id the account cannot actually reach is rejected rather than saved). Ask the user which accounts they mean; posting as the wrong Page is a public mistake. Providers: tiktok, x, youtube, threads, bluesky, telegram, reddit, pinterest, instagram, meta, google_ads, linkedin, pinterest_ads, linkedin_ads, reddit_ads, apple_ads, microsoft_ads, google_business, google_analytics, snapchat_ads, x_ads, tiktok_ads, google_tag_manager, google_search_console, bing_webmaster. Free.

set_facebook_video_cover

ChatGPT
Use this when a Facebook video or Reel the brand already published needs a new cover: a frame (coverAtMs) or a Hermoso-hosted picture (coverImageUrl). postId is the video id. Instagram covers cannot be changed after publishing. Free.

set_google_ads_bidding

ChatGPT
Change how an existing Google Ads campaign bids: MANUAL_CPC (optionally enhanced), MAXIMIZE_CLICKS (needs maxCpcUsd on an existing campaign — Google requires the CPC ceiling on that change), MAXIMIZE_CONVERSIONS, MAXIMIZE_CONVERSION_VALUE, TARGET_CPA (needs targetCpaUsd) or TARGET_ROAS (needs targetRoas, e.g. 4 = $4 revenue per $1 spent). TARGET_CPA and TARGET_ROAS are applied as Google's own v25 equivalents — maximize-conversions with a target CPA, and maximize-conversion-value with a target ROAS — so the read-back reports them as MAXIMIZE_CONVERSIONS / MAXIMIZE_CONVERSION_VALUE; report what the read-back says. The conversion-based strategies only deliver once conversion tracking is configured on the account. Changing a LIVE campaign's bidding changes what it pays immediately, so that needs confirm:true.

set_google_ads_budget

ChatGPT
Create a new daily budget, or change an existing budget’s daily amount (pass budgetResourceName). Raising the budget on a LIVE (ENABLED) campaign increases real spend immediately — you MUST show the user the new daily amount, get an explicit yes, then call with confirm:true. Creating a budget or lowering one on a paused campaign is safe.

set_google_ads_networks

ChatGPT
Change WHERE an existing Google Ads campaign serves: Google Search, Search partners (target_search_network) and the Display Network (target_content_network), each true/false. The classic use is turning Search partners OFF on a Search campaign, or Display off. Current settings are read first and a no-op says so. On a LIVE (ENABLED) campaign this moves real spend on the next auction — show the user the before -> after, get an explicit yes, then call again with confirm:true. dryRun:true validates with Google and writes nothing. The result is READ BACK from Google before you are told it took; Google’s own rules (a Display campaign cannot take Google Search on, a Search campaign keeps Google Search on) are relayed by name.

set_google_ads_status

ChatGPT
Turn a campaign, AD GROUP or AD ON (ENABLED), OFF (PAUSED) or REMOVED. Pass level:"campaign" + campaignId, level:"adGroup" + adGroupId, or level:"ad" + BOTH adGroupId and adId (Google keys an ad by adGroupId~adId). ENABLING STARTS REAL AD SPEND — you MUST first show the user the campaign name + its daily budget, get an explicit yes, then call with status:"ENABLED" and confirm:true. Pausing is always safe. REMOVED is PERMANENT in Google Ads and is handled by the same gate as delete_google_ads_object — call it once WITHOUT confirm to see what goes with it, and expect to echo back the object’s name and child count when it has children, is live, or has spent. The resulting status is READ BACK from Google before you are told it took.

set_google_ads_targeting

ChatGPT
ADD locations and languages to an existing Google Ads campaign. THIS ADDS; IT DOES NOT REPLACE — Google campaign criteria are a list, this call only ever creates entries, and there is no remove operation here. So a campaign already targeting the United States that you "change to Canada" ends up targeting BOTH and still spending in the US; the read-back names every pre-existing location and language it kept, and you MUST relay that rather than reporting the new total as the answer. Removing targeting is done in Google Ads (Campaign > Settings > Locations). Pass locations by NAME ("United States", "California", "Toronto") — they are resolved to Google's geo target ids for you; excludedLocations adds a NEGATIVE criterion (the reliable way to stop serving somewhere from here); languages takes ISO codes ("en","fr"). A campaign with NO location targeting runs WORLDWIDE, which is the most expensive default in Google Ads. Changing a LIVE campaign's targeting moves real spend immediately, so that needs confirm:true.

set_linkedin_ads_budget

ChatGPT
Change a LinkedIn campaign’s daily and/or total budget. On a LIVE (ACTIVE) campaign this changes real spend immediately — and LinkedIn can spend up to 150% of a daily budget on a high-opportunity day — so show the user the new amount, get an explicit yes, then call with confirm:true. The currency must match the ad account’s. Read back after the change.

set_linkedin_ads_status

ChatGPT
Turn a LinkedIn campaign group, campaign or CREATIVE (the ad itself) on (ACTIVE) or off (PAUSED). Pass level:"campaign" + campaignId, level:"campaignGroup" + campaignGroupId, or level:"creative" + creativeId. All three tiers must be ACTIVE for an ad to serve — activating only the campaign leaves a DRAFT creative sitting there showing nothing. ACTIVATING STARTS REAL AD SPEND — you MUST first show the user the campaign and its budget, get an explicit yes, then call with status:"ACTIVE" and confirm:true. Pausing is always safe. The resulting status is READ BACK from LinkedIn along with its servingStatuses before you are told it took: LinkedIn defers validation on drafts, so activation is exactly where a hidden problem surfaces, and the note reports what LinkedIn actually says rather than what was requested.

set_merchant_inventory

ChatGPT
SET the price, availability or stock level of one Shopping product at ONE physical store (kind:"local", keyed by storeCode) or in ONE region (kind:"regional", keyed by region). This is what stops a Shopping ad advertising something the nearest store has sold out of. Google's insert REPLACES the whole entry, so this tool reads what is already stored, merges your fields over it, and reports which stored fields it preserved; pass an explicit null to CLEAR one. The API publishes no enum for availability, so it accepts anything and only Google's local inventory data specification says which values actually serve: In stock, Limited availability, On display to order, Out of stock, submitted in English. An unrecognised value is warned about here rather than refused, because refusing would mean inventing an enum Google does not publish. Google states it can take up to 30 minutes before the product itself reflects the change, so a product read straight afterwards can still show the old value and be correct. Free.

set_meta_campaign_status

ChatGPT
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.

set_microsoft_ads_auto_apply

ChatGPT
Switch Microsoft’s auto-apply on or off, per recommendation type. SWITCHING ONE ON IS UNLIKE EVERY OTHER WRITE IN HERMOSO: it is STANDING PERMISSION FOR MICROSOFT TO CHANGE THIS AD ACCOUNT ON ITS OWN, indefinitely, with nobody watching, publishing search and multimedia ads it wrote under the brand’s name, DELETING negative keywords so the account starts buying searches it currently blocks, or creating and changing conversion goals so every smart-bidding strategy on the account re-aims. It has no expiry and no schedule you can inspect, and nothing in Hermoso will ever turn it off for the user. AND THERE IS NOTHING TO PREVIEW: Microsoft decides later, on evidence that does not exist yet, so no count, no dry run and no list can tell you what it will do, which is exactly why a blast-radius echo would be meaningless here and each type has to be named instead. To turn any type ON: confirm:true AND confirmTypes listing every type being turned on, individually. There is no "all" and no default; naming four of five leaves the fifth refused BY NAME. TURNING IT OFF IS NEVER GATED, set those types to false and it goes straight through, because that only ever reduces what Microsoft does unattended. A type already in the state you asked for is not sent at all. THE ANSWER IS THE READ-BACK: the result re-reads Microsoft and says done / not done / could not tell PER TYPE, and you report that, never the fact that the call returned. 0 credits.

set_microsoft_ads_budget

ChatGPT
Change a Microsoft Advertising campaign’s DAILY BUDGET (in the account’s currency). Raising it on a LIVE (Active) campaign increases real spend immediately — you MUST show the user the new daily amount, get an explicit yes, then call with confirm:true. If the campaign is on a SHARED budget its amount is read-only here and this refuses with an explanation rather than pretending to change it. Read back after the change.

set_microsoft_ads_profile_targeting

ChatGPT
Target LinkedIn profiles on a Microsoft Advertising Search, Shopping or Dynamic Search Ads campaign: add companies, industries, job functions, seniorities or job titles, each with an optional bid adjustment (whole percent, -90 to 900), or change an existing adjustment by criterion id (list_microsoft_ads_profile_targeting shows it). Find profile ids with search_microsoft_ads_profiles. On an ACTIVE campaign this changes bids on the very next auction, so show the user the changes, get an explicit yes, then call with confirm:true. The reply is what Microsoft reads back, and a change that does not read back is named. Removing a profile criterion is not available here; an adjustment of 0 has no effect on bids.

set_microsoft_ads_status

ChatGPT
Turn a Microsoft Advertising campaign, AD GROUP or AD on (Active) or off (Paused). Pass level:"campaign" + campaignId, level:"adGroup" + adGroupId, or level:"ad" + BOTH adGroupId and adId. 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. Microsoft has only these two statuses — its Deleted state is internal-only and cannot be SET — so to remove something use delete_microsoft_ads_object, which is a real delete operation, not a status. The resulting status is READ BACK from Microsoft before you are told it took — and Microsoft may report BudgetPaused / BudgetAndManualPaused / Suspended instead, which the note names explicitly.

set_openai_ads_budget

ChatGPT
Change a ChatGPT Ads campaign’s budget — a daily cap, a lifetime cap, or both, in the ad account’s currency. A LIFETIME (total) budget is PACED by ChatGPT Ads to spread spend evenly across the campaign’s dates (actual daily spend still varies with available delivery), so a total budget is not a first-come-first-served pool. ChatGPT Ads’ own minimum for a DAILY budget is 25.00 (measured live 2026-08-05; a LIFETIME budget has no such floor, so use one to spend less than that in total). Raising it on a LIVE (active) campaign increases real spend immediately, so you MUST show the user the old and new amounts, get an explicit yes, then call with confirm:true. Lowering it or changing a paused campaign is safe. The campaign is READ BACK after the change and the note is built from that.

set_openai_ads_daily_spend_limit

ChatGPT
Create or change the ACCOUNT-WIDE DAILY SPENDING LIMIT on ChatGPT Ads: the most all campaigns together may spend each day, renewing at midnight in the account timezone, until it is removed or reaches its optional end date. OpenAI offers it only on ad accounts with postpaid INVOICE billing and it needs billing-management permission; a card-billed account is refused whatever key is used. amount is per day in the account currency and is required even when only the end date changes (0 stops account spend). A NEW limit starts tomorrow or later: list_openai_ads_spend_windows names the earliest start date. On an existing limit leave startDate out, because its start cannot change. An account can hold SEVERAL non-overlapping daily rules (list_openai_ads_spend_windows returns each with a windowId): pass windowId to change one, or newRule:true (with startDate) to add another; with more than one rule OpenAI requires the windowId. endDate is exclusive; noEndDate:true clears it. A daily limit and a date-range window cannot overlap. Lowering it can stop delivery and raising it lets campaigns spend up to their own budgets, so show the user the current and new values first: without confirm:true AND expectedRevision (the revision list_openai_ads_spend_windows returns) nothing changes and the reply states what would. The result is read back from ChatGPT Ads.

set_openai_ads_spend_window

ChatGPT
Create (no windowId) or edit (windowId) an account-level spend-limit window on ChatGPT Ads: startDate YYYY-MM-DD inclusive, endDate YYYY-MM-DD EXCLUSIVE (the window ends the day before), amount in the account currency (sent as micros), optional name and insertion-order id (ioId). A window is a CEILING on what the whole account may spend in that range; it never makes anything spend, and campaign budgets still apply underneath it. On edit, pass only the fields that change; a window whose canEdit is false (already started, per OpenAI) cannot be edited. Raising the amount lets campaigns spend up to their budgets, so show the user the old and new amounts. Reply carries the stored window (amount, spent so far, status). The live API serves this path since at least 2026-09-12 (it answered "Invalid URL" on 2026-09-03). It needs postpaid INVOICE billing and billing-management permission; a card-billed account is refused whatever key is used.

set_openai_ads_status

ChatGPT
Turn a ChatGPT Ads AD ACCOUNT, campaign, AD GROUP or AD on (active), off (paused), or ARCHIVE it. Pass level:"account" (NO id — the connection IS the account), level:"campaign" + campaignId, level:"adGroup" + adGroupId, or level:"ad" + adId. LEVEL "account" IS THE EMERGENCY STOP AND BEHAVES DIFFERENTLY FROM EVERY OTHER LEVEL: BOTH directions take confirm:true, because pausing it stops ALL ChatGPT Ads delivery for the brand at once and activating it un-suppresses EVERY active campaign underneath — Hermoso cannot tell you in advance which of them will start spending. An account cannot be archived. ACTIVATING STARTS REAL AD SPEND — you MUST first show the user the object and its budget, get an explicit yes, then call with status:"active" and confirm:true. Pausing is always safe and stops all spend. ARCHIVING IS IRREVERSIBLE: it is this platform’s only teardown (there is no delete and no un-archive, and OpenAI’s own guidance is "only archive objects you have no further use for"), so it ALSO requires confirm:true — prefer pausing unless the user is certain. Remember an ad only serves when the ad, its ad group AND its campaign are all active and the ad has passed OpenAI’s review. The resulting status is READ BACK from OpenAI before you are told it took.

set_pinterest_ads_budget

ChatGPT
Change a Pinterest campaign’s budget — a DAILY cap or a LIFETIME cap, in the ad account’s currency. Pinterest allows only one of the two per campaign, so passing both is refused rather than silently picking one. Raising it on a LIVE (ACTIVE) campaign increases real spend immediately — you MUST show the user the new amount, get an explicit yes, then call with confirm:true. Read back after the change.

set_pinterest_ads_status

ChatGPT
Turn a Pinterest campaign, AD GROUP or AD on (ACTIVE) or off (PAUSED) — and, because Pinterest has NO DELETE anywhere in its API, this is also the only way to retire one (ARCHIVED). Pass level:"campaign" + campaignId, level:"adGroup" + adGroupId, or level:"ad" + adId. ACTIVATING STARTS REAL AD SPEND, and ARCHIVING is effectively a delete: both require you to show the user exactly what changes, get an explicit yes, and call again with confirm:true. Pausing is always safe. The resulting status is READ BACK from Pinterest before you are told it took.

set_post_refill

ChatGPT
Turn AUTOPILOT POSTING (autoposting, the posting refill) on, change it or pause it: "post 2 times a day on Instagram and Bluesky about …, let me review first", "make it 1 a day", "switch to auto-publish", "pause autopilot". PASS ONLY WHAT CHANGES. It always makes NEW posts (a fresh image, carousel or video for each) and never re-posts the Library. WHERE: every connected channel by default (channels narrows it); HOW OFTEN: the brand’s posting times, and channelPostsPerDay sets fewer on a channel (e.g. {"x":1}). There are no credit limits to set — get_post_refill shows roughly what it costs a day. Switching it on is refused when no channel is connected or nothing a day would be made. SWITCHING IT ON ASKS THE BRIEF ONCE: a goal (followers / sales / launch / other) is required; content pillars (get_post_refill suggests some from the brand), formats (mix / video / carousel / image) and an avoid list are optional. Posts are written from the brief, the brand profile, what has worked on each channel (compared only within a channel), the brand’s skills and playbooks and the ads it saved. Ask the user for anything missing (goal, mode, channels) rather than guessing. mode picks what happens to each batch: "review" (the default) keeps the fresh posts as drafts until a human approves them (run_post_refill approve), "auto" schedules them straight away. enabled:false is the PAUSE — it removes the recurring job outright, and posts already queued are left alone (cancel those with cancel_scheduled). Three posting times means up to three posts a day; postsPerDay sets fewer, and to post MORE per day pass postingTimes (one time per post a day) — e.g. "2 a day" on a brand with 3 posting times is postsPerDay:2, "4 a day" needs four postingTimes.

set_product_image

ChatGPT
Lock an image as the ad's real PRODUCT photo and SAVE it as this brand's default product, so every later plan_ad / render_ad / generate_image grounds on the true packaging without being told again. Pass imageUrl = a product shot's URL — an image from a prior research result (an organic Instagram/TikTok post, a scraped page image), a workspace / list_product_photos url, or any public product photo. The server downloads it and runs a product+safety check: a lifestyle/scene shot with no clear product, or an off-category / unsafe image, is REJECTED and NOTHING is locked or saved (the summary says why). On PASS it persists the photo to a DURABLE url, writes it to the brand's product library as the DEFAULT, and READS THE BRAND BACK to confirm — savedToBrand and the summary report what the brand ACTUALLY holds now, never what was asked for, so if it did not become the default you are told instead of finding out from a paid render. Bills one vision check. Reads YOUR saved brand for the category match (pass brandId to target a specific profile — switches this key's active profile like use_brand).

set_reddit_ads_status

ChatGPT
The one switch that arms real money on Reddit. Pass kind ("campaign", "ad_group" or "ad") plus the object id. ACTIVE starts real spend as soon as Reddit approves — show the user exactly what will run and get an explicit yes, then call again with confirm:true. PAUSED is always safe and never gated. REDDIT HAS NO DELETE OPERATION for these three: removal is a status, and ARCHIVED/DELETED here run the SAME blast-radius gate as delete_reddit_ads_object (call that one instead when you mean to remove something — it is the same code path and its unconfirmed call reports what goes with it). Remember Reddit’s three tiers all have to be ACTIVE for a single impression to serve: activating the campaign alone does nothing if its ad group and ad are still paused. The resulting status is READ BACK from Reddit.

set_role

ChatGPT
Change a workspace member’s role — admin (full access incl. billing) or member (read-only on billing). A privilege change: confirm the exact person + new role with the user, then call with confirm:true.

set_snapchat_ads_budget

ChatGPT
Change the budget on a Snapchat AD SQUAD. BUDGETS LIVE ON THE AD SQUAD, not on the campaign — a campaign-level ask is refused by name rather than silently patching nothing. NEEDS confirm:true, and without it NOTHING CHANGES: you get a sentence naming the ad squad AS READ FROM SNAPCHAT and the amount, which you must show the user first. State a plain amount in dailyBudget and Hermoso converts to micro-currency; never pass both units. The new budget is READ BACK — report what Snapchat stored, in both micro and real money, not what you sent.

set_snapchat_ads_status

ChatGPT
THE ONE SWITCH THAT ARMS REAL MONEY ON SNAPCHAT. ACTIVE starts real spend on the next auction; PAUSED stops it. EVERY change needs confirm:true, and WITHOUT confirm NOTHING CHANGES — you get a sentence naming each object AS READ FROM SNAPCHAT, with its real name and its current status, which you must show the user before asking for a yes. Confirming proves the caller meant to change SOMETHING; only reading the object back proves they aimed at the right one. EVERY TIER must be ACTIVE for a single impression to serve — a live ad under a paused ad squad shows nothing. THE ANSWER IS THE READ-BACK: report what Snapchat STORED per id, never the status you asked for. Snapchat has NO delete status — removal is a real DELETE verb, so use delete_snapchat_ads_object.

set_tiktok_ads_brand_safety

ChatGPT
Set what content this TikTok ad account’s ads may appear next to. THREE RULES TIKTOK IMPOSES AND ONE THING THAT CANNOT BE UNDONE. (1) The suitability controls move as a TRIO: TikTok requires brandSafetyType, categoryExclusionIds and verticalSensitivityId "simultaneously", so read the current values with get_tiktok_ads_brand_safety and send all three — a partial update is refused here rather than silently doing something else. (2) NO_BRAND_SAFETY is a tier TikTok REPORTS but does not ACCEPT: the write enum is EXPANDED_INVENTORY / STANDARD_INVENTORY / LIMITED_INVENTORY only, so an account can be moved off it through the API and not back onto it. (3) Category exclusions and vertical sensitivity are valid only under STANDARD or LIMITED. Warning: coverAllObjectives IS A ONE-WAY DOOR — TikTok: "Once set to true, this setting cannot be updated back to false." Turning it on widens the settings from Reach / Video Views / Community Interaction to every objective, permanently, so that direction is confirm-gated and nothing else here is. AND MIND THE SCOPE: TikTok applies this to future Smart+ campaigns and explicitly NOT to regular campaigns created through /campaign/create/, which is what create_tiktok_ads_campaign uses. The reply is TikTok’s own stored row read back, so it reports what actually changed rather than what was sent. EXPANDED_INVENTORY is allowlist-only per advertiser; if TikTok refuses it the remedy is your TikTok rep, not a different field.

set_tiktok_ads_budget

ChatGPT
Change the budget on a TikTok campaign (level:"campaign") or ad group (level:"adgroup"), in the advertiser’s own currency. It needs confirm:true, and CALLING IT WITHOUT confirm CHANGES NOTHING and returns the sentence naming the object and the new amount — show the user that first. Everything Hermoso creates on TikTok is forced paused, so a budget change on one of those spends nothing; on an object somebody has ENABLED it takes effect on the next auction, and LOWERING the budget is the fastest way to slow real money down short of set_tiktok_ads_status(status:"DISABLE"). The new budget is READ BACK from TikTok — report what it returns, not what you sent, and say so plainly when the read-back could not confirm it.

set_tiktok_ads_rule_status

ChatGPT
TURN_ON IS THE SWITCH THAT HANDS A RULE TO TIKTOK TO RUN UNATTENDED, and it is gated in two classes because the two are not the same act. A rule whose only actions are TURN_OFF, MESSAGE or a DECREASE cannot start or raise spend — the worst it does is under-deliver, which is recoverable by turning it off — so it needs confirm:true and nothing else. A rule that can TURN_ON an object or INCREASE / ADJUST_TO a budget or bid is standing permission to spend real money with nobody present, so it needs confirm:true AND confirmScope set to the scopeToken that list_tiktok_ads_rules prints. That token is computed from the rule AS TIKTOK STORES IT: confirm:true proves the caller meant to arm SOMETHING, and only the echo proves they aimed at the rule they actually inspected rather than one a teammate has edited since. CALLING WITHOUT THE GATE CHANGES NOTHING and returns the sentence describing exactly what the rule will be able to do — show the user that, get an unambiguous yes, then confirm. TURN_OFF and DELETE only ever reduce what runs unattended, so both take confirm:true and no echo; prefer TURN_OFF, because TikTok publishes no way to restore a deleted rule and turning one off is reversible. THE ANSWER IS THE READ-BACK: the result carries the status TikTok stored per rule, and says so when it could not confirm. Free.

set_tiktok_ads_status

ChatGPT
THE ONE SWITCH THAT ARMS REAL MONEY ON TIKTOK. Pass level ("campaign", "adgroup" or "ad"), the ids, and a status: ENABLE starts real spend on the next auction, DISABLE stops it, DELETE removes the objects (TikTok models removal as a STATUS — it publishes no delete verb — which is why delete_tiktok_ads_object is a thin wrapper over this same route). EVERY status change here needs confirm:true, and CALLING IT WITHOUT confirm CHANGES NOTHING and hands back the sentence describing exactly what would happen — show the user that, get an unambiguous yes, then confirm. EVERY TIER HAS TO BE ENABLED FOR AN IMPRESSION TO SERVE: Hermoso creates all three paused, so enabling the campaign alone does nothing while its ad group and ad are still disabled, and each level is a separate call. Ids can be passed in bulk, but TikTok’s QPS is 1 and calls are serialized, so a long list is simply slow. THE ANSWER IS THE READ-BACK: the result carries what TikTok STORED per id, plus a note when the read-back did not return every id — repeat that rather than the status you asked for.

set_tiktok_post_ad_authorization

ChatGPT
Turn TikTok's 'Ad authorization' setting ON or OFF for a post the authorized account owns — THIS IS WHERE A SPARK ADS AUTHORIZATION CODE COMES FROM. For somebody ELSE's post the code still comes from that creator, copied out of the TikTok app. Turning it on mints the code, which you then hand to authorize_tiktok_ads_spark_post so an ad account may promote the post. IT IS CONFIRM-GATED ON THE WAY ON, and not because it spends: TikTok changes the post's privacy to 'Available for Ads', sends it to their ad review team, says the post 'may also appear as an ad on third party platforms', and treats the call as accepting their Advertising Content Terms on the owner's behalf. Turning it OFF needs no confirm — but TikTok REFUSES to turn it off while an active Spark Ad is using the post, so pause those campaigns first. authorizationDays must be one of 7, 30, 60, 180, 365. THE ANSWER IS THE READ-BACK: the reply carries the post's real authorization status and its code, because TikTok's own response body is empty. NEEDS THE TIKTOK ACCOUNT AUTHORIZATION (see tiktok_account_status).

set_tiktok_smart_status

ChatGPT
Enable, pause or delete Smart+ campaigns, ad groups or ads (up to 20 ids per call). ENABLE starts REAL SPEND on the next auction and DELETE is permanent (a deleted campaign takes its ad groups and ads with it), so both require confirm:true after the user says yes. DISABLE (pause) is never gated. The reply is read back from TikTok and names any id whose new state it could not confirm.

set_x_ads_status

ChatGPT
Pause or ACTIVATE an X ads CAMPAIGN (campaignId) or ONE LINE ITEM inside it (lineItemId) — pass exactly one. ACTIVATING STARTS REAL SPEND on the next auction, so it requires confirm:true — this is the only switch on X that arms money. Tell the user the budget and what will start spending BEFORE you pass confirm. DELIVERY ON X IS THE AND OF BOTH LEVELS: an ACTIVE line item under a PAUSED campaign serves nothing, so the result reads the PARENT back too and states whether anything can actually spend rather than letting you infer it. Pausing a line item is the REVERSIBLE way to take one ad group out of delivery — deleting it is not.

set_x_chat_admin_settings

ChatGPT
Use this when a group the brand admins should limit actions to admins: adminOnly {addMember, editGroupInfo, sendMessage, startCall, editMessageTtl, blockScreenCapture}. sendMessage:true makes it announcement-only. Group ids start with g. Needs X connected; X bills per call, so it costs credits.

set_x_chat_disappearing_messages

ChatGPT
Use this when messages in a conversation should disappear after a time, for everyone in it. disappearAfter like 24h, 7d, 90m or a number of seconds. Needs X connected; X bills per call, so it costs credits.

set_x_chat_screenshot_setting

ChatGPT
Use this when screenshots in a conversation should be blocked or announced, for everyone in it. Pass blockScreenshots OR notifyOnScreenshot, one per call. Needs X connected; X bills per call, so it costs credits.

set_youtube_playlist_image

ChatGPT
Use this when a playlist should get a custom cover. imageUrl must be Hermoso-hosted (a render, a make_thumbnail result, or the user’s own file through upload_file). Needs a verified YouTube channel. Free.

set_youtube_thumbnail

ChatGPT
Set the CUSTOM THUMBNAIL on a video already on the connected channel, using a Hermoso image — a make_thumbnail render, a generated image, or a frame. The thumbnail is the single biggest lever on YouTube click-through and YouTube otherwise auto-picks a frame, so a published video without one is leaving reach on the table. It changes ONLY the thumbnail — video, title and privacy are untouched — but it is public and immediate, so show the user which image is going on which video and get a yes first. Custom thumbnails require a VERIFIED YouTube channel (a phone number at youtube.com/verify); without it YouTube refuses and the error says so. Images over YouTube’s 2MB cap are compressed automatically. The image must be Hermoso-HOSTED, which is not the same as Hermoso-GENERATED: the user’s own artwork works, put it through upload_file first and pass the URL that returns. An arbitrary external host is refused. 0 credits. Needs a connected YouTube channel.

snapchat_ads_report

ChatGPT
Read Snapchat ad performance — impressions, spend, swipes and video quartiles — at ad account, campaign, ad squad or ad level. THREE THINGS TO KNOW BEFORE CALLING: granularity is required (TOTAL is the default); DAY and HOUR granularity REQUIRE startTime and endTime AND both must land exactly on the start of an hour, which Snapchat refuses otherwise and Hermoso refuses for free before spending the call; and SPEND COMES BACK IN MICRO-CURRENCY, so divide by 1,000,000 before quoting money to anyone. Read-only, free.

start_x_broadcast

ChatGPT
Use this when a broadcast scheduled with manualPublish:true should go on air NOW, publicly, as the brand. Needs confirm:true after the user approved the exact broadcast and stream. Needs X connected; X bills per call, so it costs credits.

stitch_video

ChatGPT
Render a multi-scene STITCHED video (≥2 scenes) — ONLY for spots LONGER than ONE clip of the chosen model. A multi-beat ad that FITS one clip renders better and cheaper as ONE single-pass generate_video/render_ad (a single generation carries the whole hook->demo->payoff arc) — never stitch those. What fits is the model’s own maximum from hermoso_capabilities, not a fixed number: 15s on most models, 30s on the longest-clip one, so a 30s spot need not be stitched at all if you name that model. Blocks until done. Spends credits.

store_get

ChatGPT
Read one of this workspace’s data stores by key, for visibility into what the app holds — playbooks, swipefile, saved locations, avatars, creations, chats, brand, memory, skills. Read-only, free. Allowed keys: heist.memory.v1, heist.skills.v1, heist.playbooks.v1, heist.avatars.v1, heist.locations.v1, heist.chats.v1, heist.creations.v1, heist.assets.v1, heist.brand.v1, adInspo.swipefile.v1. (The typed tools — list_memory / list_skills / get_brand — are friendlier for those; use store_get for the rest.)

stripe_balance

ChatGPT
The brand's Stripe balance (available and pending, per currency) and the most recent payouts with status and arrival date. Read-only, free. Needs Stripe connected (Settings > Connectors > Stripe).

stripe_report

ChatGPT
Revenue from the brand's OWN Stripe account: gross, refunds, net and succeeded-charge count per day/week/month, new customers in the window, and active subscriptions + MRR where the key can read them. This is the money side of the loop — read an ad, a hook or a launch against real revenue instead of clicks. Default window is the last 30 days; a window with no charges answers a real zero, not a failed read. Read-only, never moves money, free. Needs Stripe connected (Settings > Connectors > Stripe).

submit_bing_webmaster_content

ChatGPT
Give Bing a page's content DIRECTLY instead of waiting for it to crawl — for a page Bingbot renders badly or cannot reach. YOU PASS THE HTML AND NOTHING ELSE: Microsoft's httpMessage parameter is a base64 raw HTTP response whose status line and every header must end CRLF with exactly two CRLFs before the body, and an LF-only message is accepted with a 200 while indexing nothing useful — a silent wrong answer — so Hermoso builds that framing and computes Content-Length itself. Optional structuredData carries JSON-LD for non-HTML content such as images or PDFs; dynamicServing is left at "none" unless the site really does serve different content per device, because declaring otherwise is a claim about the customer's infrastructure. This spends the CONTENT submission budget, which is separate from the URL one and is read first — check both with bing_webmaster_submission_quota. Max 10MB per submission. 0 credits.

submit_bing_webmaster_sitemap

ChatGPT
Register a sitemap with Bing so it discovers new and changed pages by itself — the durable twin of submit_bing_webmaster_urls, which spends a small per-site daily quota every time. Takes the sitemap's FULL URL, not a path. Bing accepts Sitemap, RSS 2.0, Atom 0.3, Atom 1.0 and plain text files (Microsoft's own list). A sitemap hosted on ANOTHER host is not refused — Bing allows that when the other host is also verified — but it is called out, because it is the likeliest reason one silently never crawls. SubmitFeed returns no body whatsoever, so the answer is read back from Bing's own feed list and reports what Bing says rather than that the call returned. 0 credits.

submit_bing_webmaster_urls

ChatGPT
Submit URLs to Bing for indexing — the fastest way to get a new or changed page crawled by Bing, and therefore into Copilot and ChatGPT web search. THE SITE'S REMAINING DAILY QUOTA IS READ FIRST and a batch that would exceed it is refused with the real number rather than half-submitted, because Bing rejects an over-quota batch as a whole and a partial failure leaves you unable to say which URLs made it. Bing accepts at most 500 per batch (Microsoft's own limit) and the site's daily quota is usually far lower. THE CONFIRMATION IS BING'S QUOTA COUNTER MOVING: its submit methods return no body at all, so the answer reports the counter before and after rather than claiming success from a 200. 0 credits.

submit_indexnow

ChatGPT
Ping the IndexNow search engines that pages were added, updated or deleted — Bing, Yandex, Naver, Seznam.cz and Yep, all reached by ONE submission. Important: GOOGLE DOES NOT PARTICIPATE IN INDEXNOW and never has, so say that plainly rather than letting anyone expect a Google effect; for Google use submit_search_console_sitemap and search_console_url_inspection. NO CONNECTION IS NEEDED, because the credential belongs to the USER: they generate a key, host it as a plain text file on their own web server, and any engine fetching that file is the whole ownership check. HERMOSO VERIFIES THAT FILE BEFORE SUBMITTING — that it is reachable and that its contents are exactly the key — and refuses with an explanation if not, because we have no write access to a customer's web root and cannot place it for them. If the key file is moved off the site root with keyLocation, it authorises ONLY URLs under that directory, and URLs outside it are refused before anything is sent (IndexNow rejects the whole batch for one out-of-scope URL). Up to 10,000 URLs per submission, all on the one host. HTTP 202 is a SUCCESS ("received, validation pending"), not a reason to retry. 0 credits.

submit_search_console_sitemap

ChatGPT
Tell Google about a sitemap. feedpath is the sitemap's COMPLETE URL ("https://example.com/sitemap.xml"), never a path — Google addresses a sitemap by its full url. Re-submitting one that is already registered is harmless and simply re-notifies Google. THE ANSWER IS READ BACK FROM GOOGLE, not echoed from the request: it reports whether the sitemap is now actually registered, whether Google has processed it yet, and what errors and warnings it already carries. 0 credits.

subscribe_linkedin_leads

ChatGPT
Have LinkedIn push every new lead to Hermoso the moment it is submitted, and optionally relay each event on to the user’s own CRM. THE WEBHOOK LINKEDIN VALIDATES IS ALWAYS HERMOSO’S OWN: LinkedIn challenges it with our app secret (and re-challenges every ~2 hours), which no CRM, Zapier or Make endpoint can answer — so never promise a customer URL as the LinkedIn webhook. Pass forwardTo (public HTTPS) to have Hermoso relay each lead event there; leave it off to keep events in Hermoso only (list_linkedin_lead_events). The reply is read back from LinkedIn, not from the 201. Leads stay readable with list_linkedin_leads either way. Free.

tag_intercom_conversation

ChatGPT
Add an Intercom tag to a conversation, or remove it (remove:true). Name it by tagId or exact tagName (list_intercom_tags); a tagName the workspace does not have yet is created only with create:true (ask the user first). Internal to the inbox: the customer never sees tags. Read back. Free. Needs Intercom connected (Settings > Connectors > Intercom).

telegram_account

ChatGPT
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.

test_dm_automation

ChatGPT
Check whether a sample comment, mention or DM would trigger a rule (saved by id, or unsaved) and exactly what it would send on which channel; on X it also quotes what X charges for the send. Sends nothing. Free.

test_instagram_dm_automation

ChatGPT
Check whether a sample comment or DM would trigger a saved rule and exactly what it would send. Sends nothing. Free.

test_openai_ads_lead_form

ChatGPT
Queue an explicitly SYNTHETIC, signed test lead for a PUBLISHED lead form to the account’s lead-sync webhook, so the receiving system can be checked end to end. expectedPublishedRevisionId is the form’s publishedRevisionId. The payload is marked synthetic; never count it as a lead.

threads_insights

ChatGPT
Performance for ONE Threads post (views, likes, replies, reposts, quotes, shares) when postId is given, or for the whole ACCOUNT when it is omitted — views, likes, replies, reposts, quotes, LINK CLICKS, follower count, and follower_demographics broken down by country, city, age or gender. Note the two metric sets differ: "clicks" exists only at account level and "shares" only on a single post, and an unknown metric is refused by name rather than dropped. since/until narrow the account window (Threads has no data before 2024-04-13, and followers_count / follower_demographics are lifetime metrics that ignore a window — the reply says so when that happens). Threads returns no demographics below 100 followers; an absent block means the account is under Meta’s floor, NOT that the audience is empty.

threads_profile

ChatGPT
Read ANY public Threads account by handle — name, bio, avatar, verified badge, follower count, and its lifetime likes / quotes / reposts / views. This is competitor and creator research on Meta’s own data rather than a scraper: read a rival’s positioning straight off their profile instead of guessing it, then read what they are posting with list_threads_profile_posts and draft the brand’s answer. A bare handle ("nike"), an "@nike" and a threads.net profile link all resolve — Meta matches the handle EXACTLY, so a display name will not. IF NOTHING COMES BACK, DO NOT SAY THE ACCOUNT DOES NOT EXIST: read the note, which says whether that is a real absence or our app’s approval state. Read-only, 0 credits. Needs Threads connected.

threads_publishing_limit

ChatGPT
How much of the brand’s Threads quota is left right now — posts (250 per rolling 24 hours), replies (1,000), DELETIONS (100) and location searches (500) — each as used, total and REMAINING. Check it before any bulk operation, and read it the moment Threads starts refusing: a quota refusal is otherwise indistinguishable from a broken connection or a missing permission, and reconnecting cannot fix it. A number comes back null when Threads did not report it, never as 0 — "none left" and "we could not tell" are different answers. Read-only, 0 credits. Needs Threads connected.

tidy_memory

ChatGPT
Clean up and consolidate the workspace Memory: drops entries that are about how Hermoso, a tool, a connector or a platform API behaves (product behaviour, not the brand), drops phone numbers and emails, and merges near-duplicate facts into one sentence each. Call with no argument to get the PROPOSAL (what would be removed and merged, with reasons) — nothing changes. Call again with confirm:true to apply it through the same typed writers the app uses (deletes carry tombstones so they stay deleted on every device). One small model call; a few credits.

tiktok_account

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

tiktok_account_insights

ChatGPT
The connected TikTok account's OWN analytics: follower demographics broken down by AGE, GENDER, COUNTRY and CITY, the daily series (video views, profile views, likes, comments, shares, reached audience, engaged audience, follower gained/lost/net, and the profile-button clicks a verified Business account collects), and the lifetime counters. This is TikTok's twin of instagram_insights and youtube_channel_report. TWO PRECONDITIONS TIKTOK ENFORCES ON THE DEMOGRAPHICS, and this tool reports which one is in the way instead of returning an empty breakdown: the account must be a BUSINESS account, and it must have at least 100 FOLLOWERS. Below that TikTok withholds the distributions for privacy, which is not the same as an audience it could not measure. The look-back is capped at 60 DAYS, which is SHORTER than the 90 days the brand-mentions tools cover, and daily numbers lag by up to 48 hours. The bio, verified badge and profile link are deliberately not readable here. That needs a TikTok permission this authorization does not request, and tiktok_account on the TikTok posting connector already returns all three. NEEDS THE TIKTOK ACCOUNT AUTHORIZATION with the audience-insights permission (tiktok_account_status says whether this profile has it). Read-only, free.

tiktok_account_status

ChatGPT
Report whether this profile holds the TikTok ACCOUNT-HOLDER authorization — the SECOND, separate consent on the same TikTok app that the TikTok Ads connection uses. TikTok issues two different grants: the ADVERTISER one (campaigns, budgets, reporting — that is tiktok_ads) and this ACCOUNT one, which is what lets Hermoso read and manage the comments on the brand's own TikTok posts and mint Spark-Ads authorization codes for them. Holding one does NOT give you the other, so a workspace can be fully connected for ads and still answer 'not connected' here — that is a real third state, not a broken session. Reports the state, the TikTok business id every other tool in this family uses, the scopes the grant actually carries, and any scope MISSING from it (TikTok binds scopes at authorize time and never retroactively, so a grant made before a scope was added simply does not have it and only a reconnect fixes that). Connecting is the one step that needs a browser — the reply carries the exact URL to send the user to. Read-only, free.

tiktok_ads_audience_overlap

ChatGPT
How much a TikTok audience overlaps with up to four others — the read that answers whether a new audience is genuinely new or is mostly the people you already target, which is what stops two ad groups bidding against each other for the same person. TIKTOK SUPPRESSES ANY AUDIENCE SIZE UNDER 1,000, so a missing or blank size here means "fewer than 1,000", never "nobody" — reporting it as a zero would be a confidently wrong answer. Read-only, free.

tiktok_ads_catalog_diagnostics

ChatGPT
TikTok’s own diagnosis of a catalog — every detected issue at CRITICAL or WARNING level across product attributes, product review, catalog configuration, pixel/event problems and feed or file-upload failures, each carrying the reason AND TikTok’s suggested fix. This is the tool for "the feed ran and no products showed up" and for "my products were rejected and I do not know why". IT COVERS EVERY FEED IN THE CATALOG BY DEFAULT: naming a single feedId narrows it, and simply omitting the field at TikTok would silently report on the DEFAULT feed alone, so a multi-feed catalog with one broken feed would get a clean bill of health. Diagnostics regenerate once a day, so a fix made today may not clear until tomorrow’s run, and the reply says which day’s data it is. Read-only, free.

tiktok_ads_comment_thread

ChatGPT
The conversation around a single comment on a TikTok ad: give it an ORIGINAL comment and you get that comment plus every reply to it; give it a REPLY and you get the reply plus the comment it answers. Use it before replying to a heated thread, so you answer what was actually said rather than the one line list_tiktok_ads_comments showed. Pages up to 1000 at a time, which is ten times what the list read allows. Read-only, free.

tiktok_ads_diagnosis

ChatGPT
TikTok’s issues-and-suggestions diagnosis for the ad groups on an ad account — its own read on why delivery is underperforming, in three categories: CREATIVE (no background music, video too short, resolution too low), BID_AND_BUDGET (a suggested bid or budget, a recommendation to switch to Maximum Delivery, and the full Estimated Delivery Results tables pairing nine bid levels or fifteen budget levels with their estimated cost, conversions, CPA and impressions), and EVENT_TRACK (a pixel that has recorded nothing for seven days, which means the ad group is optimising toward an event nothing is firing). Every issue code comes back with a plain sentence saying what to DO about it. Warning: AN EMPTY ANSWER IS NOT A CLEAN BILL OF HEALTH: TikTok diagnoses ACTIVE ad groups only and omits any ad group it has no suggestions for, so a missing ad group is EITHER healthy OR not currently active and TikTok does not distinguish the two. At most 20 ad groups per call. Read-only, free.

tiktok_ads_pixel_stats

ChatGPT
Event-level statistics for one TikTok pixel over a date range — how many times each event actually fired, which is how you tell a pixel that is installed and working from one that is installed and silent. Pass the pixelId or the pixelCode from list_tiktok_ads_pixels; default range is the last 7 days (UTC), at most 30. Counts are split into browser vs server per event. Read-only, free.

tiktok_ads_report

ChatGPT
Performance for a TikTok advertiser account at campaign, ad group, ad or whole-advertiser level — spend, impressions, clicks, CTR, CPC, CPM and conversions by default, or whichever of TikTok’s metrics and grouping dimensions you name. reportType picks WHICH REPORT: BASIC is spend and performance (the default); AUDIENCE is the same money broken down by WHO SAW IT — pass an audience dimension such as age, gender, country_code or interest_category, which BASIC refuses outright. A report with ZERO rows genuinely means nothing delivered in that window — say exactly that, and never present zeros as measured performance. Read-only, free, spends nothing. NOTE: everything Hermoso creates on TikTok is created PAUSED, so a brand-new build reports nothing at all until somebody activates it with set_tiktok_ads_status(confirm:true) — check the statuses with list_tiktok_ads_campaigns before reading an empty report as bad performance.

tiktok_ads_rf_inventory_estimate

ChatGPT
Reach & Frequency INVENTORY ESTIMATE — how many people TikTok can reach, for what budget, at what frequency, over a fixed window, before anything is booked. THIS IS THE ONLY SANCTIONED SOURCE of the three numbers create_tiktok_ads_rf_ad_group needs (budget, purchasedImpression, purchasedReach), and that matters because TikTok does NOT refuse a reservation outside the range it estimated — it silently books its own maximum instead, so an invented number buys something nobody asked for. IMPRESSIONS AND REACH COME BACK IN THOUSANDS (110 means 110,000) and the create takes the same unit, so pass them back UNSCALED. Fix ONE of the three with rfPurchasedType (FIXED_BUDGET / FIXED_SHOW / FIXED_REACH) and TikTok predicts the other two; omit it to get the whole range. Reach & Frequency is ALLOWLIST-ONLY per ad account. Read-only, free, books nothing.

tiktok_ads_rule_results

ChatGPT
EVERY EXECUTION OF THE USER’S AUTOMATED RULES — when each ran, what it acted on and what it changed. THIS IS THE ONLY PLACE THEY WILL SEE IT: TikTok routes rule notification emails to the developer address registered on the app rather than to the advertiser, so an armed rule can pause campaigns or raise budgets on their account and nothing reaches their inbox. Call it with no ids to list executions (optionally filtered by rule, action, dimension or a UTC time window); pass ruleId AND execId TOGETHER, both taken from one row of that list, for the detail of a single run — TikTok keys a result on the pair, so half of one is refused rather than answered about the wrong thing. Read-only, free.

tiktok_ads_verification_status

ChatGPT
Whether a TikTok ad account or Business Center has passed business verification, when it was reviewed, and if it failed, TikTok’s own reason. AN AD ACCOUNT AND A BUSINESS CENTER ARE VERIFIED SEPARATELY and carry different statuses, so pass one or the other and never both; a verified Business Center does not verify the ad accounts inside it. Worth reading during onboarding: an unverified account meets limits that otherwise get diagnosed as something else entirely. It also reports the qualification id of any document already on file, which submit_tiktok_ads_verification can reuse. Read-only, free.

tiktok_bid_protection

ChatGPT
Whether TikTok's BID PROTECTION is covering a Smart+ campaign or ad group, and what it actually paid back. Bid protection compensates in AD CREDITS when delivery misses the bid, so this answers two different questions from one tool: with no dates it returns today's coverage STATUS per id; with startDate and endDate it returns the day-by-day HISTORY, each row carrying the credit amount and currency. Smart+ objects ONLY — a classic TikTok campaign id does not resolve here. TikTok keeps 60 days, and the two endpoints size differently: status takes up to 200 ids, history allows 200 DIVIDED BY the day span (a 10-day window takes 20 ids), which is refused here with the arithmetic done rather than by TikTok with it left to you. An id TikTok says nothing about is NAMED in the reply, because a short list otherwise reads as 'those are unprotected'. Read-only, 0 credits.

tiktok_category_benchmark

ChatGPT
What an average TikTok Business account in a given industry looks like: mean likes, comments, shares, video count, follower count, 30-day follower growth, engagement rate and video views. Pair it with tiktok_account_insights to answer 'are we ahead of our category or behind it', which neither number answers alone. These are TikTok's own cross-account averages, not this brand's numbers. businessCategory must be one of TikTok's twenty-five published values. NEEDS THE TIKTOK ACCOUNT AUTHORIZATION. Read-only, free.

tiktok_creator_info

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

tiktok_mention_top_terms

ChatGPT
The top 20 KEYWORDS and the top 20 HASHTAGS appearing in the captions of the posts that mention this brand. It is what people say when they talk about it, rather than which posts they said it in. Two TikTok endpoints behind one tool because they take identical parameters and answer the same question at two granularities; kind:'KEYWORDS' or kind:'HASHTAGS' calls only one. Counted across the top 1,000 mentioning posts of the last 90 days, so this is the language of the mentions and not of TikTok at large. Same BUSINESS-account precondition as every mentions tool. NEEDS THE TIKTOK ACCOUNT AUTHORIZATION with the brand-insights permission. Read-only, free.

tiktok_post_status

ChatGPT
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 postId when TikTok gives one), 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).

tiktok_tto_campaign_report

ChatGPT
How each creator video on a TikTok One campaign actually performed, with EVERY HEADLINE METRIC SPLIT ORGANIC VERSUS PAID: views, reach, engagement rate, likes, comments, shares, favourites, completion rate, average view time; plus audience breakdowns and any anchor clicks. That split is the question an influencer campaign exists to answer: did the creator’s own audience carry the video, or did the media spend. Give startDate and endDate TOGETHER to also get day-by-day figures; giving one without the other is refused, because TikTok requires each whenever the other is present. TikTok reports one country per call and will only accept one of the CAMPAIGN’s own countries, so it is read off the campaign rather than guessed. Two TikTok behaviours worth passing on: reporting is backfilled if a creator tags a video retroactively, and campaigns created through the API do NOT show their reporting on the TikTok One website, so this is the only place it appears. Read-only, free.

unarchive_x_chat

ChatGPT
Use this when an archived conversation should return to the brand’s X inbox. Needs X connected; X bills per call, so it costs credits.

unbind_tiktok_ads_spark_post

ChatGPT
Remove a TikTok post from this ad account’s authorised Spark Ads list — the cleanup for an authorization that has expired or been revoked. IT IS NOT REVERSIBLE FROM OUR SIDE: re-authorising needs a FRESH code from the post’s owner, who is usually not the person making this call, so getting it wrong costs a conversation with a creator rather than a second API call — which is why it is confirm-gated even though it spends nothing. Ads already built on the post keep running; what is lost is the ability to build a NEW one. Calling it without confirm CHANGES NOTHING and returns the sentence describing exactly what would happen. THE READ-BACK INVERTS: the proof is the post’s ABSENCE from the authorised list afterwards, and an id that is still there is reported as not confirmed rather than done.

unhide_meta_comment

ChatGPT
Use this when a comment the brand hid on its own Facebook or Instagram post should be visible again. Get commentId from list_meta_comments. Free.

unhide_tiktok_comment

ChatGPT
Use this when a hidden comment on the brand’s own TikTok post should be visible again. TikTok may keep it hidden if its own filters hid it; the reply reads the real status back. Free.

unlike_tiktok_comment

ChatGPT
Use this when the brand should take its like off a comment on its own TikTok post. Free.

unlock_bluesky_convo

ChatGPT
Use this when a locked Bluesky DM conversation should accept new messages again. Free.

unmute_bluesky_convo

ChatGPT
Use this when a muted Bluesky DM conversation should notify the account again. Free.

unmute_x_broadcast_chat_user

ChatGPT
Use this when a muted viewer may chat again in the brand’s live broadcast. Pass userId or username. Needs X connected; X bills per call, so it costs credits.

unmute_x_chat

ChatGPT
Use this when a muted X Chat conversation should notify the brand again. Get id from list_x_chats. Needs X connected; X bills per call, so it costs credits.

unsubscribe_meta_webhooks

ChatGPT
Remove this app’s webhook subscription from a Page, so Meta stops pushing its events. It removes the WHOLE subscription — Meta’s DELETE takes no field list — so it is all fields or none; to keep some, call subscribe_meta_webhooks again with just those. Nothing published or posted is affected; only the notifications stop. THE ANSWER IS THE READ-BACK: a subscription still present when read back means the unsubscribe did NOT take effect, and that is reported rather than the 200. 0 credits.

update_apple_ads_budget_order

ChatGPT
Change a budget order: its name, window, total value, attached ad account or invoice details. Only the fields you send change; everything omitted keeps its value. RAISING amount IS HOW YOU EXTEND A BUDGET APPROACHING EXHAUSTION — an exhausted budget order stops every campaign drawing from it, so this is the tool to reach for when campaigns stop serving with budget left on their own daily caps. AN END DATE ON AN ACTIVE BUDGET ORDER CAN ONLY BE SHORTENED, NEVER EXTENDED; the one exception is openEnded:true, which clears the end date entirely and makes the budget run indefinitely. startTime, if changed, must still be tomorrow or later. Free.

update_apple_ads_object

ChatGPT
Edit an existing Apple Ads campaign, ad group, keyword, creative or ad. Pass level and id plus ONLY the fields you want to change — Apple leaves every field you omit exactly as it is. Campaign: name, dailyBudget, startTime, endTime, countries, bidStrategyType. Ad group: name, defaultBid, startTime, endTime, searchMatch. Keyword: bid only — its text and match type are immutable, so delete and re-add to change either. Creative: name, plus (Apple Maps creatives only) a new creativeSpec — a creative’s type and destination are locked at creation, so pointing at a different app or product page means creating a NEW creative. Ad: name only — its creative and ad group are locked too, and Apple’s own instruction for serving a different creative is to create a new ad and delete the old one. Location group: name, description, and locationIds or rules. WARNING, AND IT APPLIES TO EVERY ARRAY HERE: AN ARRAY REPLACES RATHER THAN MERGES. The countries you send become the campaign’s entire geographic targeting, the targeting dimensions you send become the ad group’s entire setting for those dimensions, and Apple states it for location groups in terms — to add ONE shop to a group, read its current locationIds, append, and send the whole list back, or you have just removed every other shop. Send everything you want to keep, never just the new one. This tool deliberately CANNOT change a status: enabling arms real spend, so it lives behind set_apple_ads_status and its confirmation. Free.

update_applovin_ads_campaign

ChatGPT
Rename an AppLovin campaign or change its budget, goal value, countries, US states or metros, end date or landing page, read back. On a LIVE campaign a budget, goal, country or end-date change needs confirm:true. Status is set_applovin_ads_status; goal type, bidding and start date cannot change. Needs the Campaign Management key, which exists once AppLovin enables API access on the account; without it the answer says how to get it.

update_applovin_ads_creative_set

ChatGPT
Edit an AppLovin creative set, read back: rename it, change its countries or languages (an empty list means all), its landing page (url; "" falls back to the campaign’s), replace its assets (assetIds, checked against AppLovin’s own asset types first), or add it to more campaigns (addToCampaignIds, up to 20). Adding a LIVE set to a LIVE campaign needs confirm:true. Status is set_applovin_ads_status. Needs the Campaign Management key, which exists once AppLovin enables API access on the account; without it the answer says how to get it.

update_brand

ChatGPT
Patch SPECIFIC fields of the active profile (name, domain, sells, summary, category, audience, positioning, voice, style, goal, logos, brand/product name pronunciations, profileType: explore = kept but unused) WITHOUT overwriting the rest — a read-modify-write on the saved brand. Use for “change our voice to playful”, “we sell to dentists now”. To onboard a brand from scratch, use draft_brand. If no brand is saved yet and you only INFERRED one from what the user is making, ask them to confirm it is their brand before saving it (they may be working for a client or just trying things). Only pass the fields you’re changing.

update_business_location

ChatGPT
Change the brand’s Google Business Profile listing — hours, phone, website, description, categories, service area, labels, store code, address or the business name. THIS EDITS THE PANEL ON GOOGLE SEARCH AND MAPS, immediately and publicly: there is no draft, no preview and no undo. Pass ONLY what changes, in fields, keyed by Google’s own field names: websiteUri, phoneNumbers, regularHours, specialHours, moreHours, profile, categories, storefrontAddress, title, labels, storeCode, openInfo, serviceArea, serviceItems, latlng, adWordsLocationExtensions, relationshipData. CALL IT WITHOUT confirm FIRST — nothing is written, Google validates the payload for you, and you get back the CURRENT value of every field you are about to change, so you can show the user the exact before-and-after; then call again with confirm:true once they approve. Changing the business NAME (title) or ADDRESS (storefrontAddress) additionally needs confirmName set to the listing’s CURRENT name, because Google can suspend a listing over either. Output-only fields (metadata) and immutable ones (languageCode) are refused by name rather than dropped. Use dryRun:true to validate a payload with Google and write nothing. Needs Google Business Profile connected.

update_doc

ChatGPT
EDIT a Google Doc — the correction append_to_doc cannot make. Two shapes: replacements:[{find, replace}] rewrites specific text wherever it appears (call read_doc first and match the text EXACTLY; matchCase:false ignores case), or rewrite:"…" replaces the ENTIRE body (rewrite:"" empties it), or dropdowns:[{title, value}] sets a dropdown chip (e.g. Status -> Approved — read_doc lists every chip with its options; pass dropdownId when two share a title; an unknown title or option is refused with the real list and nothing changes); the same entry with options:[…] (the COMPLETE new list, 2–50) and/or newTitle changes the dropdown itself for every chip built from it, and an option you drop that a chip shows needs replace:{"Old":"New"}. Find/replace runs immediately and REPORTS how many occurrences changed — zero matches is reported as a FAILURE to match, never as a quiet success, because a text edit that silently does nothing is worse than one that visibly fails. A whole-body rewrite is destructive: call it without confirm first to get the character count, then confirm:true + confirmCells. Both are index-free by design — an agent cannot reliably compute Google’s character offsets, and a wrong offset deletes the wrong sentence.

update_drive_file

ChatGPT
Update a Drive file: rename (name), move it into a folder (moveToFolderId, optionally removeFromFolderId to move OUT of the old one), or trash / untrash it (trash:true|false). Pass fileId (from list_drive_files). To delete permanently, use delete_drive_file.

update_linkedin_conversion_rule

ChatGPT
Rename a LinkedIn conversion rule, change its attribution windows or attribution model, or ENABLE/DISABLE it. Disabling is how a conversion rule is retired — LinkedIn publishes no delete for one, and while it is disabled every streamed event for it is refused. The rule’s type is immutable. The note quotes what LinkedIn returned on the read-back, not what was sent — repeat that. Free.

update_meta_catalog

ChatGPT
Change a product catalog’s settings — most often its NAME. Also accepts the default and fallback product image URLs (what Meta shows when a product row has no usable image), the dynamic-ad display settings, and the destination / flight / store / partner-integration settings for travel and local-inventory catalogs. A catalog’s VERTICAL and its owning business portfolio are permanent and cannot be changed here — Meta publishes no way to change either. THE 200 IS NOT THE ANSWER: Meta replies {"success":true} and never returns the catalog, and it says exactly the same thing for a field it quietly ignored — so the summary is built from a fresh READ-BACK, and if a value does not read back as sent you are told it was NOT confirmed. Free, 0 credits.

update_meta_object

ChatGPT
Update an EXISTING campaign, ad set, or ad — rename, change its daily budget, retarget (ad sets), or change status (PAUSED / ACTIVE / ARCHIVED). Pass objectId (from list_meta_ads) + adAccountId. Setting something ACTIVE can start REAL AD SPEND — show the user what will run + its budget, get a yes, then pass confirm:true. Pausing and renaming are always safe and always reversible. ARCHIVING IS NOT: Meta treats an archived object as DELETED and refuses to bring it back — every later edit answers "This campaign has been deleted, so you can only edit the name" (measured live 2026-08-05), archiving a campaign takes its ad sets and ads down with it, and the only way back is to duplicate it as a new object. Use PAUSED unless the user has said they are finished with it for good.

update_onedrive_file

ChatGPT
Update a OneDrive item: rename (name) and/or move it into a folder (moveToFolderId). Pass fileId (from list_onedrive_files). To remove an item, use delete_onedrive_file.

update_openai_ads_account

ChatGPT
Update the ChatGPT Ads account’s names, primary URL and/or favicon. brandName is shown IN ADS, accountName is internal to Ads Manager, legalName is the business. Per OpenAI, a brand-name change PAUSES AD DELIVERY until brand review passes and a legal-name change restarts business review and may pause serving; an account name triggers no review. So a brand or legal change needs confirm:true: without it nothing changes and the reply names the old and new values and the pause, which the user must agree to first. Legacy name sets all three (cannot be combined with them). An unchanged name is not sent. Also url, faviconUrl (public image or home page, ≥256×256) or faviconFileId. An account whose brand review is not approved cannot serve: re-read it before promising anything runs. A 403 “not enabled” is OpenAI’s account setting, not a Hermoso failure.

update_openai_ads_feed_products

ChatGPT
Update the PRICE, TITLE or AVAILABILITY of variants already in a ChatGPT Ads product feed — send only what changed instead of re-uploading the catalog. This is what stops a stale feed serving ads for out-of-stock items. Pass feedId and products:[{id, variants:[{id, title?, price?:{amount,currency}, availability?:{available|status}}]}], where products[].id is the PARENT product id and variants[].id the existing variant id, both from the catalog already in the feed. price.amount is an INTEGER IN MINOR UNITS — 8999 means $89.99 — so a decimal is a hundredfold error in the price you advertise, and is refused. IT UPDATES EXISTING VARIANTS ONLY: it never creates a feed, uploads a catalog, or adds a product that is not already there. ACCEPTED IS NOT LIVE — OpenAI applies the change asynchronously and returns no completion timestamp and no downstream result, so an out-of-stock product keeps serving until it propagates; say that rather than reporting it as done. A 403 naming product_feed_api_disabled / product_feed_delta_api_disabled means the ACCOUNT lacks Feeds access — nothing about the request was wrong and retrying it unchanged will not help. A 404 means the feed id is wrong, the feed is not linked to this ad account, or Feeds is not enabled.

update_openai_ads_feed_settings

ChatGPT
Rename a product feed and/or set searchEligibleRespected (its organic ChatGPT search listing follows each product’s is_eligible_search flag). Touches no ads or spend; the reply is what OpenAI stored.

update_openai_ads_lead_form

ChatGPT
Save a NEW DRAFT REVISION of a ChatGPT Ads lead form (the whole definition: name + fields are replaced). expectedDraftRevisionId must be the draftRevisionId you last read — ChatGPT Ads refuses a stale one so two editors cannot clobber each other. The published version is untouched until publish_openai_ads_lead_form.

update_openai_ads_object

ChatGPT
EDIT an existing ChatGPT Ads object in place — rename it, change a campaign’s budget or geo targeting, rewrite an ad group’s context hints or bid, or replace an ad’s title, body, landing page or image. Pass level:"campaign" + campaignId, level:"adGroup" + adGroupId, or level:"ad" + adId. Only the fields you pass are changed, but note that context hints, bidding, TARGETING and the creative are REPLACED WHOLESALE rather than merged, so send the complete list. A patch that would silently DESTROY something is refused by name with what would have been lost, rather than going through: dropping a campaign’s geo, custom audiences or PLATFORM targeting, deleting an ad group’s max bid, or demoting a maximize_* bid strategy to a fixed bid. Changing the budget, the bid or the creative of a LIVE (active) object changes what real money buys immediately — show the user the old and new values, get an explicit yes, then pass confirm:true. The object is READ BACK after the change.

update_pinterest_ads_ad

ChatGPT
EDIT an existing Pinterest ad in place — its name, DESTINATION URL, creative type, tracking URLs, deep links, lead form, or which ad group it sits in. A PARTIAL edit: a field you do not name is left exactly as Pinterest has it. THE PIN CANNOT BE SWAPPED ON A REAL AD — Pinterest documents pinId as updatable "only for draft ads", so to promote a different Pin create a new ad in the same ad group with create_pinterest_ads_ad and archive this one; that is refused up front with the way through rather than after Pinterest rejects it. IT DOES NOT CHANGE STATUS — set_pinterest_ads_status owns ACTIVE / PAUSED / ARCHIVED, and passing status here is refused by name. EDITING AN AD SENDS IT BACK THROUGH PINTEREST’S REVIEW: the read-back reports the review status and says so when it moved, and an ad in review is NOT serving — never report an edited ad as live on the strength of the edit succeeding. Free.

update_pinterest_ads_ad_group

ChatGPT
EDIT an existing Pinterest ad group in place — its name, bid, budget, pacing, placement and, above all, its TARGETING. Before this, targeting was frozen the moment an ad group was created: a mistyped bid or a missing country meant building the whole tree again, and because PINTEREST HAS NO DELETE every correction left a permanent archived shell behind. This is a PARTIAL edit — a field you do not name is left exactly as Pinterest has it, so send only what changes, and never re-send everything you just read (that would overwrite a concurrent edit). targetingSpec is the exception: it REPLACES the whole targeting object, so include every criterion you still want. IT DOES NOT CHANGE STATUS — set_pinterest_ads_status owns ACTIVE / PAUSED / ARCHIVED, and passing status here is refused by name. Changing the bid, the budget, or which campaign it spends from WHILE IT IS LIVE moves real money on the next auction: show the user the exact new value, get an explicit yes, then pass confirm:true. Pinterest refuses targetingTemplateIds alongside targetingSpec / trackingUrls / autoTargeting / placementGroup, which is refused here naming the conflicting pair. The ad group is READ BACK from Pinterest afterwards and the note describes what Pinterest actually stored — print it verbatim. Free.

update_reddit_ads_ad

ChatGPT
Rename a Reddit ad, point it at a different post, or change its click url. Does not activate or pause it — use set_reddit_ads_status. Swapping the post changes what people see, so confirm the new creative with the user first. The result is read back from Reddit.

update_reddit_ads_ad_group

ChatGPT
Change an existing Reddit ad group’s name, budget, goal type, bid, schedule dates or targeting. Budget and bid are ordinary amounts in the ad account’s currency. Targeting MERGES KEY BY KEY — measured live 2026-08-05, and it is NOT a wholesale replace: a key you send replaces that whole list, a key you LEAVE OUT is kept exactly as it was, and an explicit empty array (geolocations: []) is the only way to clear one. So passing just {communities:[…]} does NOT drop an existing geo or interest filter on an ad group that holds the budget — name every key you want gone. This does NOT activate or pause anything; use set_reddit_ads_status for that. The result is read back from Reddit.

update_reddit_ads_audience_users

ChatGPT
Add people to, or remove people from, a Reddit custom audience. Pass ordinary email addresses and/or mobile advertising ids — each one is canonicalised the way Reddit specifies and SHA-256 hashed on our server before it is sent, so raw customer data never reaches Reddit, and an identifier you already hashed is passed through untouched. Up to 2,500 rows per call; send bigger lists as repeated calls and the audience accumulates. EVERY ROW MUST CARRY THE SAME FIELDS: Reddit’s upload is positional, so if some rows have an email and others do not, the values shift into the wrong column and match nobody — split those into separate calls instead. After Reddit accepts the upload the size does not move for up to 4 hours and processing can take 36, so never re-send the same batch because the count looks unchanged. Free.

update_reddit_ads_campaign

ChatGPT
Change an existing Reddit campaign’s name, spend cap, budget, goal type or flight dates. Amounts are ordinary numbers in the ad account’s currency. The OBJECTIVE and useCatalog cannot be changed: Reddit freezes both at creation and refuses a change even from an old objective name to its new one (IMPRESSIONS to BRAND_AWARENESS), so create a new campaign instead. This does NOT activate, pause or archive anything — use set_reddit_ads_status for that. The result is read back from Reddit before you are told it took.

update_reddit_ads_max_template

ChatGPT
Change a Reddit Max template ad: its assets (5 to 45 ACTIVE, at least 3 HEADLINE and 2 IMAGE or VIDEO, read and counted before anything is sent), thumbnails, destination URL, supplementary text, user-generated content enrollment or name. The reply is the template read back, including whether Reddit regenerated the ads. Pausing, activating or archiving it is set_reddit_ads_status.

update_reddit_ads_post

ChatGPT
Turn comments ON or OFF on an existing Reddit ad post. THAT IS THE ONLY EDIT REDDIT ALLOWS: its post-update schema permits exactly one field, allow_comments, and REQUIRES it — headline and body both answer “Additional fields not permitted” once a post is published (measured live 2026-08-05). So a copy change is not an edit at all: create a new post with create_reddit_ads_post and point the ad at it with update_reddit_ads_ad, or fix the wording in Reddit’s Ads Manager. Never promise to reword a live post. A REDDIT AD POST CANNOT BE REMOVED THROUGH THE API AT ALL: Reddit publishes no delete endpoint for one and its update schema has no status, archived or deleted field (re-checked against Reddit’s own reference on 2026-08-05), so creating one is a one-way door and the only way to take it down is Reddit’s Ads Manager. Say that plainly rather than offering to delete it. Turning comments off is publicly visible on a post people may already be replying to, so confirm it with the user first.

update_reddit_ads_saved_audience

ChatGPT
Rename a Reddit saved audience or replace its targeting. Targeting MERGES KEY BY KEY — measured live 2026-08-05, not a wholesale replace: a key you send replaces that whole list, a key you LEAVE OUT is kept as it was, and an explicit empty array (geolocations: []) is the only way to clear one — so name every key you want gone. Editing one that live ad groups already use re-targets all of them immediately, so say how many are affected and get a yes before changing targeting on a running account. The result is read back from Reddit.

update_saved_creator

ChatGPT
Set the OUTREACH STATUS and/or a NOTE on a creator already saved in the swipefile (find_creators -> save_to_swipefile, or the heart on a creator card). Status is one of new | contacted | replied | booked | passed. The note is free text (deal terms, rate, what was sent). Reads back the updated row. Use list_swipefile to find the key. Free.

update_settings

ChatGPT
Change this account's app settings. language = the language EVERY ad, script, plan and answer is written in from now on (say the language in plain English, e.g. "German", "Japanese", "Brazilian Portuguese") — it applies to renders made over MCP as well as in the app. theme = the app's appearance, "dark" or "light". watchEmail = the weekly competitor-watch email on/off. Only pass what you are changing. Account-wide (every profile), and it takes effect on the next call.

update_sheet

ChatGPT
CORRECT cells in a Google Sheet — write values to an exact range, overwriting whatever is there. This is the fix append_to_sheet cannot make: appending only ever adds rows at the bottom, so without this a wrong number stays wrong forever and the only "correction" is a second row contradicting the first. Pass range (e.g. "B2:C5", or "Q3 Report!B2" to name a tab — list_sheet_tabs gives the names) and values as an array of row arrays; an anchor cell like "B2" is fine and the block is written down and right from it. Writing into EMPTY cells goes straight through. Writing OVER cells that already hold values is REFUSED first, naming exactly how many filled cells would be overwritten — show the user that, get a yes, then call again with confirm:true. The result is READ BACK from the sheet, so what you report is what the sheet now holds rather than what Google accepted.

update_tiktok_ads_audience

ChatGPT
Rename a TikTok custom audience. THAT IS ALL IT CHANGES, and the limit is TikTok’s rather than ours: their only other update path replaces the uploaded customer FILE, which Hermoso deliberately does not build (hashed personal data under TikTok’s own Terms, over a transport this integration does not have, where a wrong hash yields a silently EMPTY audience). Changing WHO is in an audience means creating a new one. The new name is READ BACK from TikTok because their update returns an empty body, so a bare success proves nothing — if the read-back disagrees with what you sent, the reply says so and the read-back is the truth.

update_tiktok_ads_rule

ChatGPT
Replace an automated rule’s WHOLE definition. TikTok’s update is a full replace rather than a patch, so send every field the rule should keep — anything omitted is gone. IF THE RULE IS CURRENTLY RUNNING AND THE NEW VERSION CAN START OR RAISE SPEND, this arms it the moment it saves, so it takes the same confirm:true plus confirmScope echo as arming one: an edit must never be a route by which a harmless rule becomes a spending rule behind a gate it passed when it meant something else. A rule that is currently OFF needs no gate here, because arming it later runs the gate against the new definition. Free.

update_tiktok_tto_campaign

ChatGPT
Add creators or ad accounts to an existing TikTok One campaign. IT ONLY EVER ADDS: TikTok appends what you pass to what the campaign already holds and publishes nothing that removes either, so there is no way to un-invite a creator or unlink an ad account through the API: the reply therefore reports what the campaign NOW HOLDS rather than what you sent. The name, description, brand and anchor cannot be changed after creation at all; TikTok’s own create endpoint accepts a campaign_id and then ignores every field except the account and the handles, which is why a rename is impossible rather than merely unsupported here. handleNames is not accepted on a BRAND_LINK campaign, because a brand-level link is open to whoever opens it. Free.

update_x_ads_campaign

ChatGPT
Change a LIVE X campaign — budget, name, delivery pacing. THIS IS HOW YOU THROTTLE OR RAISE SPEND on a running campaign without rebuilding it, and lowering dailyBudget is the fastest way to slow money down short of pausing. Budgets are in the ad account's own currency. Only fields X actually applies are offered: startTime/endTime are DEPRECATED on an X campaign (its schedule lives on the LINE ITEM — use update_x_ads_line_item) and frequency capping needs an X account feature we cannot enable, so both are refused BY NAME with the reason instead of being sent and silently ignored. THE READ-BACK IS A DIFF against the before-state: a field X did not move is reported as REFUSED, never counted as applied. Print the returned note verbatim.

update_x_ads_line_item

ChatGPT
Change an X line item — BID, bid strategy, SCHEDULE, goal or name. An X campaign's start and end times are deprecated, so the schedule genuinely lives HERE. Lowering bidAmount is also the fix when X refuses a campaign budget with “Bid is too close to Budget”. NOT changeable after creation: objective and productType — X answers 200 and silently keeps the old value (measured), so both are refused BY NAME here and a different objective means a NEW line item. Changing goal also requires bidAmount (X refuses the goal alone) and that is stated up front rather than relayed. A line-item dailyBudget/totalBudget is only legal when the parent campaign is NOT budget-optimised — that is the campaign's setting, so those two are forwarded and X's own refusal names the remedy rather than Hermoso blocking a legal edit. THE READ-BACK IS A DIFF against an independent re-read: a field X accepted but did not move is reported as REFUSED. Print the returned note verbatim.

update_x_broadcast

ChatGPT
Use this when a scheduled X broadcast needs a new time, title or setting. Only the fields you pass change. Turning manualPublish off needs confirm:true. Needs X connected; X bills per call, so it costs credits.

update_x_chat_settings

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

update_youtube_caption

ChatGPT
Use this when an uploaded caption track needs new text, a new name or its draft state changed. Tracks YouTube generated itself cannot be edited. Get captionId from list_youtube_captions. Free.

update_youtube_channel

ChatGPT
APPLY THE BRAND TO THE CHANNEL ITSELF — banner art, description, keywords, country and the trailer non-subscribers see. Every other YouTube tool brands the videos; this brands the page they sit on. Under the hood channels.update is a PUT, so the CURRENT settings are read and merged first — otherwise setting a description would silently wipe the channel's keywords, country and trailer. AND YOUTUBE SILENTLY IGNORES SOME FIELDS, channel title above all (usually only changeable in YouTube Studio): the result DIFFS what YouTube actually stored against what was asked for and reports anything that did not stick in notApplied. DO NOT REPORT THOSE AS CHANGED — a 200 is what YouTube accepted, not what it stored. A banner must be a Hermoso render (jpeg or png, under 6MB); YouTube re-crops it per device, so the safe area is the middle 1235x338 of a 2048x1152 image. Public and immediate — show the user what is going on the channel first. 0 credits. Needs YouTube connected.

update_youtube_channel_section

ChatGPT
Use this when an existing homepage shelf should change or move. It REPLACES the section with what you pass, publicly and immediately. Get sectionId from list_youtube_channel_sections. Free.

update_youtube_playlist

ChatGPT
Use this when a playlist’s title, description or privacy should change. An omitted field is left as it is. Setting privacy to private takes a playlist out of public view without deleting it. Get playlistId from list_youtube_playlists. Free.

update_youtube_video

ChatGPT
Edit an existing video on the connected channel: title, description, tags, and/or privacy (unlisted | public | private). THIS IS HOW YOU FLIP AN UNLISTED UPLOAD PUBLIC (post_to_youtube is public by default; unlisted only when the user asked for it). Making a video PUBLIC puts it on the channel where anyone can find it, so show the user exactly what will change and get an explicit yes before calling with privacy:"public". Fields you omit are left untouched. Needs a connected YouTube channel.

upload_apple_ads_asset

ChatGPT
Upload an image into the Apple Ads asset library for an APPLE MAPS brand creative. APPLE MAPS ONLY, and that is Apple’s rule rather than ours: it refuses uploads for App Store apps outright, because an App Store ad is rendered from the app’s product page in App Store Connect and not from an image anyone uploads. That refusal is stated up front instead of being sent and rejected, so if you want a different-looking App Store ad the answer is App Store Connect, not this tool. PNG, JPG or HEIC. Pass imageUrl (upload_file turns a local file into one) and brandId, which list_apple_ads_brands returns. Apple processes the upload asynchronously, so re-read it with list_apple_ads_assets until its eligibility says it is ready before referencing it in a creative. Uploading spends nothing. Free.

upload_applovin_ads_assets

ChatGPT
Upload videos, images or an HTML playable to AppLovin from Hermoso render or Library urls or public files (mp4, mov, jpg, png, gif, html; up to 40). Returns AppLovin asset ids once processed; pass uploadId to check a pending upload again. New assets go through AppLovin review. Needs the Campaign Management key, which exists once AppLovin enables API access on the account; without it the answer says how to get it.

upload_file

ChatGPT
Persist an ARBITRARY user file (image, video, audio, PDF or document, up to 150MB) into Hermoso and get back a durable public URL that EVERY publish, schedule and ad-build tool accepts — post_to_meta / post_to_linkedin / post_to_linkedin_page / post_to_youtube / post_to_tiktok / post_to_pinterest / post_to_x / post_to_google_business / schedule_post / upload_meta_asset / upload_google_ads_asset / create_meta_ad / create_linkedin_ads_creative / set_youtube_thumbnail / save_to_drive / save_to_onedrive. THIS IS THE BRING-YOUR-OWN-CREATIVE PATH: it is for files that have NOTHING to do with a Hermoso render (media on the user's desktop, an agency's finished ad, a photo they shot), and it means you can publish, schedule and run ads through Hermoso without generating anything here. Provide exactly ONE source — passing two is an error, never a silent preference: url (ANY public http(s) link — Hermoso fetches it server-side, so nothing crosses this connection and there is no practical size limit; THIS IS THE ONE THAT ALWAYS WORKS, including on the hosted connector), dataUri (a base64 data: URI — keep it under ~15MB, since the bytes travel over this connection). If the file is already at a public https URL, the Meta, Reddit and ChatGPT-Ads tools take it directly and re-host it safely — but LinkedIn (posts and ad creatives), Pinterest, the YouTube thumbnail and upload_google_ads_asset upload the BYTES themselves and therefore refuse an external host, so run it through here first and pass the URL this returns. When in doubt, use this: its URL works everywhere. (Calling the underlying HTTP route directly? POST /api/upload takes the file's RAW BYTES as the request body with its own content-type — NOT multipart/form-data — or ?url=<link> with no body.) Returns {url, kind, bytes}.

upload_google_ads_asset

ChatGPT
Add a creative to a Google Ads account’s ASSET LIBRARY so it can be used in ads. It does NOT have to be a Hermoso render — the user’s own creative is the normal case; the URL just has to be Hermoso-HOSTED because we fetch the bytes, so run any file of theirs through upload_file and pass the URL it returns. For an IMAGE, pass imageUrl (≤5MB); an arbitrary external/CDN URL is refused. For VIDEO, Google Ads uses YouTube-hosted videos — post the video to YouTube as UNLISTED first (post_to_youtube with privacy:"unlisted" — link-only, not public or searchable, and unlike "private" it CAN run as an ad), then pass its youtubeVideoId here. Returns the asset resource name. Pass customerId (from list_google_ads_campaigns).

upload_meta_asset

ChatGPT
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.

upload_reddit_ads_assets

ChatGPT
Store creatives in the Reddit Ads ASSET LIBRARY for a profile: images and videos (from a public https url — Reddit downloads it; a video may carry a posterUrl), HEADLINE text, and CTA labels from Reddit’s fixed list (Shop Now, Learn More, Sign Up, …). 1–20 per call. Processing is asynchronous: the reply is read back from the library after polling (up to ~20s) and says which assets are ACTIVE, which Reddit REJECTED and why, and which are still processing (finish those with get_reddit_ads_asset_uploads). A Reddit MAX campaign builds its ads from these assets: pass the ACTIVE ids to create_reddit_ads_max_campaign (at least 3 HEADLINE and 2 IMAGE or VIDEO). A standard post-based ad does not take an asset id. Free.

upload_tiktok_ads_creative

ChatGPT
Put a finished creative onto the TikTok AD ACCOUNT so an ad can point at it — this is the bridge between making an ad and running one, and create_tiktok_ads_ad has no other source for the ids it needs. Pass the public https url of a Hermoso render (every render already is one) or any other public https file; TikTok fetches it from its own servers, so a local path, a data: url or anything on http:// is refused here for free. A VIDEO UPLOAD ALSO UPLOADS ITS OWN COVER and hands back coverImageId — pass that straight to create_tiktok_ads_ad as imageIds, because TikTok requires a cover on a video ad and rejects any whose dimensions differ from the video. CHECK usableInAds BEFORE BUILDING THE AD: TikTok happily accepts a creative it will then refuse to build an ad from, and says so only in this flag — a 720x1280 render is fine and a 496x864 one is not, so a false here means re-render larger, and the note says so. Uploading the same file twice is fine; TikTok refuses a repeated file NAME, so leave fileName out unless you want a specific label in TikTok Ads Manager. Free — TikTok charges nothing for storage and Hermoso bills no credits for this.

upload_tiktok_comment_image

ChatGPT
Turn a public image URL into the imageUri that comment_on_tiktok_video needs — TikTok will not take a raw URL on a new comment, only on a reply. Returns imageUri, imageWidth and imageHeight, and ALL THREE must be passed back together: TikTok rejects dimensions that do not match what it stored. Limits are TikTok's own — at most 5 MB, JPG/JPEG/PNG/WebP, between 360x360 and 1080x1920 (or 1920x1080). upload_file turns a local file into a URL this accepts. NEEDS THE TIKTOK ACCOUNT AUTHORIZATION (see tiktok_account_status).

upload_youtube_caption

ChatGPT
Use this when a video on the connected channel needs a subtitle track. Pass the SRT or WebVTT file body as text, not a URL, and the real language code (never guessed). draft:true keeps it unpublished for review. Free.

upscale_video

ChatGPT
Upscale a video to higher resolution (2x) for final delivery. Paid render; returns the served URL. A 480p Seedance 2.5 draft: finish_draft (same take, native 1080p). Two engines: the default ('standard') is the safe precision upscaler; engine:'flux' is the FLUX 3 video upscaler (1080p/2K/4K) with an optional mode:'creative' detail-enhancement pass — pick it when the user asks for the FLUX upscaler or wants added detail rather than a faithful enlargement.

upsert_merchant_product

ChatGPT
Create or replace a product in a Merchant Center feed — this is how a merchant's catalogue gets populated, and it is what Shopping ads, free listings and retail Performance Max actually serve. It is an UPSERT and a WHOLE-ROW write: calling it again for the same offerId REPLACES the product rather than patching it, so send every field you want kept each time. You do not have to find a feed first — omit dataSource and it resolves the account's one writable feed, creates one if there is none, and REFUSES by name if there are several rather than putting the product in a market the campaigns may not target. offerId, contentLanguage and feedLabel together ARE the product's identity and are all immutable; contentLanguage and feedLabel are taken from the feed when the feed declares them. For the product to serve at all Google needs title, description, link, imageLink, availability and price — a row missing any of them is stored and then disapproved, and the reply says so rather than letting you believe it published. The reply reports what GOOGLE STORED, not what was sent. Free: a feed edit cannot spend.

use_brand

ChatGPT
Pin which profile this connection acts on. Pass the profile id or exact name from list_brands. Works for your own profiles AND one another account SHARED with you (pass its name or the profile id list_brands prints); access is verified against your real membership before it is pinned. Persists for this API key until changed, on every surface (hosted connector included — no environment variables, no restart).

vercel_web_analytics

ChatGPT
Vercel WEB ANALYTICS for one project: page views and unique visitors over a window (default the last 30 days, production traffic), and with by a breakdown by requestPath (which pages), referrerHostname (where visitors came from), country, deviceType, osName, browserName, route, utmSource / utmMedium / utmCampaign / utmContent / utmTerm (which campaigns and ads brought them), or a time step (day, week, month). Use it to see which landing pages and ad campaigns actually bring traffic. Needs Web Analytics turned on for the project in Vercel. Read-only, free. Needs Vercel connected (Settings > Connectors > Vercel).

verify_bing_webmaster_site

ChatGPT
Ask Bing to CHECK the ownership proof for a site already on the account. IT DOES NOT PLACE THE PROOF — nothing can over an API. The user puts an XML file at the site root, a meta tag in the home page <head>, or a CNAME DNS record, and Bing Webmaster Tools shows the exact filename and value for each. A negative answer therefore means "the proof is not in place yet", never that Hermoso or the connection failed, and the reply says which three options exist. Bing refuses to verify a site that was never added, which is knowable for free, so that is refused here with add_bing_webmaster_site named as the fix. The verdict is read back from the site LIST rather than taken from the call's own return value — the two can disagree. 0 credits.

x_account

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

x_ads_report

ChatGPT
Performance stats for X campaigns, line items or promoted posts. placement here is SINGULAR and comes from a FOUR-value set (ALL_ON_TWITTER / PUBLISHER_NETWORK / SPOTLIGHT / TREND) — deliberately not the twelve placements a line item accepts; do not carry one across. THE TRAP THIS REPORTS: an entity id that does not exist on the account answers 200 with every metric null, which is indistinguishable from a real zero, so an all-null response is FLAGGED rather than narrated as zero performance. Max 20 ids per call. Read-only, free.

x_chat_settings

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

x_mentions

ChatGPT
Read the posts mentioning the connected X account — who is talking to the brand, in their own words, newest first. Use it to find what deserves a reply (reply with post_to_x + replyToId) and to mine real objections and customer language for ad copy. COSTS CREDITS PER MENTION RETURNED, plus one account lookup — keep maxResults small (default 10) and tell the user the cost before pulling a big page. Needs X connected.

x_post_metrics

ChatGPT
THE X ANALYTICS TOOL THAT WORKS — impressions, likes, reposts, replies, quotes and bookmarks for any post, PLUS the advertiser numbers (link clicks, profile clicks, engagements) for YOUR OWN posts published in the last 30 days. X serves those private metrics on this same lookup with the user-context connection you already have; that is X's own design, not a workaround. Use this rather than x_post_insights, whose endpoint family X has retired. If a post is deleted, protected or suspended, X answers with no data at all and this says so — that is MISSING DATA, never zero engagement, and must never be reported as a measured zero. Costs a small number of credits (X bills per API read). Needs X connected.

x_search_counts

ChatGPT
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.

x_user

ChatGPT
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.

youtube_bulk_report

ChatGPT
THE ONLY PLACE YOUTUBE PUBLISHES THUMBNAIL IMPRESSIONS AND THUMBNAIL CTR. This is a different API from youtube_channel_report — YouTube’s bulk Reporting API — and for a product that generates thumbnails it is the number that says whether the thumbnail actually worked. Reports: thumbnails (impressions + CTR per video per day) · thumbnails_by_source (the same, split by traffic source, traffic source DETAIL, device and OS) · cards (per-card impressions, clicks and click rate by card_id) · end_screens (per end-screen element) · traffic_source (with the UNCAPPED traffic_source_detail — youtube_channel_report caps that at 25 rows) · basic. IT IS SCHEDULED, NOT ON-DEMAND, AND THIS IS THE ONE THING YOU MUST EXPLAIN TO THE USER: the first call SCHEDULES a job and returns NO DATA. YouTube then writes one CSV per 24-hour Pacific day — the first within 48 hours — plus a backfill of the 30 days before scheduling, and files expire after 60 days. It can NEVER answer about a period before the job existed, so "we have no thumbnail history yet" is a real and correct answer on day one. An unknown report name is refused with the list. Zero rows means missing data for that window, never zero impressions. Read-only, 0 credits.

youtube_channel

ChatGPT
Read the brand’s connected YouTube channel — title + subscriber / view / video counts (for reporting), and whether CUSTOM THUMBNAILS are on (customThumbnails: enabled | needs_verification | unavailable | unknown). When they are not on, the reply carries YouTube’s own step-by-step to turn them on (a phone verification in YouTube Studio), so check this before promising a custom thumbnail. Needs a connected YouTube channel.

youtube_channel_report

ChatGPT
The YouTube Analytics reports that say WHERE views came from, WHO watched and WHERE they stopped watching — the questions youtube_channel (totals) and youtube_video_insights (one video, flat) cannot answer. Pick a report: day / month (time series) · country / province (US states) / city / dma (geography) · trafficSource (search vs browse vs suggested vs shorts feed vs external — the single most useful one for judging a thumbnail and title) · trafficSourceDetail (the actual search terms, inside ONE source — pass parent, e.g. "YT_SEARCH") · playbackLocation / playbackLocationDetail (which sites embedded it) · device / operatingSystem · demographics (age + gender) · sharingService · subscribedStatus · audienceRetention (the drop-off CURVE, 100 points across ONE video — the read that tells you whether the hook held and exactly when people left) · topVideos (the channel’s best in the window). Scope it to one or more videoIds, or omit for the whole channel. An unknown report name is refused WITH the list rather than quietly swapped. TWO THINGS TO SAY OUT LOUD WHEN REPORTING: demographics returns viewerPercentage and NOTHING else — YouTube publishes no absolute demographic counts, so never convert it into a number of viewers — and a capped report (city 250, topVideos 200, the *Detail reports 25) is the TOP N, not the whole set. Zero rows means missing data for that window, never zero views. Read-only, 0 credits.

youtube_video_insights

ChatGPT
Per-VIDEO performance for a video on the connected channel — views, estimated minutes watched, average view duration, average view PERCENTAGE (the retention number that tells you whether the hook held), likes, comments, shares and subscribers gained. Use it for "how did that video do", "which upload performed best", or to judge an ad before spending more behind it. youtube_channel only returns channel-wide totals and cannot answer this. Defaults to the last 28 days; pass startDate/endDate (YYYY-MM-DD) for another window. Read-only, 0 credits. Needs a connected YouTube channel.

Capabilities

WritesInteractive

Example Prompts

Click any prompt to copy it.

App Stats

879

Tools

3

Prompts

ChatGPT

Platforms

Works with

ChatGPT

Data refreshed daily