- Brand
- Hermoso AI
- Category
- Marketing
- Primary Subcategory
- AI Search & LLM Visibility (AEO/GEO)
Integration details
Description
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.
- Integration type
- Plugin
- Verification status
- Not applicable
- Platform
- ChatGPT
- Primary Subcategory
- AI Search & LLM Visibility (AEO/GEO)
- Secondary Subcategories
- None listed
- Brand
- Hermoso AI
- Access
- Account required
- First tracked
- 2026-10-16
- Tool count
- 247
- Market
- US
The Primary Subcategory used for this profile’s headline score.
Other Subcategories where the Integration is listed.
Your score is coming
ChatGPT now suggests Plugins on its own when they match a user's request.Your Plugin Discovery Score measures how often yours appears, and it will show here as soon as it’s ready.
What discovery looks like

Get alerts for Hermoso AI
Get updates when Hermoso AI’s Discoverability Score or category rank changes.
Competing in ChatGPT AI Search & LLM Visibility (AEO/GEO)
View Category247 tools agents can invoke
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_snapchat_ads_status
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_linkedin_ads_status
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_meta_campaign_status
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_microsoft_ads_status
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_openai_ads_status
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_pinterest_ads_status
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_ads_status
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_reddit_ads_status
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_ad_group
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_ad_group
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_subtitles
Where this brand's ALERTS go and whether they are arriving: for each alert channel (Slack, Discord, a generic webhook) whether it is connected, which event groups it receives (renders, posts, approvals, watch, credits, leads — default all), its health (delivering / NOT DELIVERING / stopped because the channel was deleted or revoked — reconnect by pasting a new URL in the app), whether the webhook is signed, and the last deliveries with their HTTP status and any retry queued. Read-only, free.
list_alert_settings
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.
analyze_video
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. On the Free plan, paidOnly is true and note explains the status. Read-only, free.
get_post_refill
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_campaign
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_scheduled
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_budget
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_budget
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 / edit_linkedin_post / delete_linkedin_post / delete_x_post, not rescheduled.
reschedule_post
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_role
Change this account's app settings. language = the language EVERY ad, script and plan is written in from now on (chat replies follow the language the user writes in) (say the language in plain English, e.g. "German", "Japanese", "Brazilian Portuguese") — it applies to renders made over MCP as well as in the app. theme = the app's appearance, "dark" or "light". watchEmail = the weekly competitor-watch email on/off. Only pass what you are changing. Account-wide (every profile), and it takes effect on the next call.
update_settings
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.
change_voice
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.
openai_ads_report
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_ad_policy
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_account_status
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_connector_accounts
Choose which event groups one alert channel receives. provider: slack, discord or webhook. events: any of renders (a render finished or failed), posts (a scheduled post published, partly published or failed), approvals (autopilot drafts waiting for review), watch (new competitor ads found by the competitor watch), credits (credits running low), leads (a new lead-form lead from Meta, LinkedIn or Snapchat), connections (an account connected to or disconnected from the brand: account.connected / account.disconnected), inbox (a new Facebook or Instagram comment or direct message: comment.received / message.received; opt-in, never on unless named) — or "all". An empty list keeps the channel connected and sends it nothing. Unknown names are refused by name. Read back from the stored setting. Free.
set_alert_events
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.
clip_video
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.
clone_static
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 the same ad-library reads search_meta_ads does, longest-running = proven winners). Auto-tailors the white space + counter-plays to YOUR saved brand. Spends credits (free when you pass ads).
competitor_teardown
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. OpenAI creative enhancements (on by default for self-serve campaigns since 2026-10-30) may serve this copy rewritten, translated or on another page of the same site; the switch is in ChatGPT Ads Manager (account and campaign) and the API does not expose it, so never promise the exact words will run.
create_openai_ads_ad
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
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 / documentUrl (a PDF/PPTX/DOCX document ad) 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. LEAD GEN ADS: under a LEAD_GENERATION campaign pass leadFormId (create_linkedin_lead_form makes one) — LinkedIn requires it there. The whole tree — group, campaign and every creative — is read back from LinkedIn before you are told anything exists.
create_linkedin_ads_creative
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_campaign_group
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_campaign
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_adset
Build a retargeting audience on a connected Meta ad account. Four 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. kind:"customerfile" is the brand's OWN CUSTOMER LIST (emails, phones, names…) for retargeting, suppression or as a lookalike seed: pass customerFileSource (a required attestation of where the data came from: USER_PROVIDED_ONLY, PARTNER_PROVIDED_ONLY or BOTH_USER_AND_PARTNER_PROVIDED), plus schema (the column names in order, from EMAIL, PHONE, FN, LN, FI, CT, ST, ZIP, COUNTRY, DOB, DOBY, GEN, MADID, EXTERN_ID) and rows (arrays in that order, up to 10,000 per call) to upload them in the same call. Hermoso normalizes and SHA-256-hashes every value before it leaves the server, exactly as Meta requires, and reports Meta’s own match counts (a mostly-rejected upload is a format problem and says so). To ADD more people to an existing list, call again with kind:"customerfile" + audienceId + schema + rows (uploads add, they never replace). Meta asks the ad account to accept its Custom Audience Terms once; if it has not, Meta’s refusal says so.
create_meta_audience
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_pinterest
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
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_ad_group
Create the top tier of a TikTok ad — the campaign, which fixes the OBJECTIVE everything under it optimises toward. CREATED PAUSED AND THERE IS NO OVERRIDE, and TikTok is the one platform where that is NOT the vendor’s own behaviour: TikTok creates ENABLED by default, so Hermoso forces operation_status DISABLE and overrides a caller who asks for ENABLE. It spends nothing until set_tiktok_ads_status(confirm:true). OBJECTIVES that create cleanly, each proven with a real create: TRAFFIC (the default), REACH, VIDEO_VIEWS, ENGAGEMENT, WEB_CONVERSIONS, LEAD_GENERATION, APP_INSTALL, PRODUCT_SALES, CONVERSIONS. RF_REACH (Reach & Frequency) also works but ONLY with budgetMode BUDGET_MODE_INFINITE — TikTok’s own words are "The budget type for Reach & Frequency must be unlimited". Five further values sit in TikTok’s schema enum and are REFUSED BY NAME with the reason (CATALOG_SALES is retired, SHOP_PURCHASES needs TikTok Shop on the advertiser, and APP_PROMOTION / RF_ENGAGEMENT / RF_APP_INSTALL are withheld) — the server owns that list, so a refusal names its cause and is TikTok’s restriction rather than a Hermoso fault. budgetMode is BUDGET_MODE_DAY (the default), BUDGET_MODE_TOTAL, BUDGET_MODE_DYNAMIC_DAILY_BUDGET or BUDGET_MODE_INFINITE. A campaign alone can never serve: build an ad group under it, then an ad. The result is READ BACK from TikTok and says so when the read-back could not be run.
create_tiktok_ads_campaign
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_brand
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_dm_automation
Return the credit balance, recent priced calls with their credits, and costModel (the one-sentence rule of what costs credits). THE RULE: only AI model runs and Ad Spy research spend credits; publishing, scheduling, ads management, analytics, comments, DMs and connectors cost no credits on any plan (X is the single per-call exception; xCost gives its exact credits per post, with and without a link). Check before kicking off paid generation; answer "does posting cost credits?" with NO. HOW MANY posts is set by the plan, not credits: the Free plan includes 10 posts a month on up to 3 channels, and a post counts once however many channels it goes to (`posting` says how many are used); every paid plan posts without a limit and runs autopilot (Basic $9.99/mo: unlimited posting and Autopilot, no monthly credits; it includes the same 250+ free credits as the Free plan, monthly credits start on Starter, and Autopilot spends credits). Starter, Pro and Studio: media models at cost + 10% (memberRate); Basic and Free pay standard prices.
hermoso_credits
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_dm_automations
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_dm_automation
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_comment
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_creator
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_skill
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_playbook
Hermoso AI ChatGPT Plugin FAQ
How the directory, categories and Discoverability Score work.
Read the methodologyHow do I improve Hermoso AI's ChatGPT Plugin discoverability?
The levers are the listing surface agents actually read: names, descriptions, keywords, tool metadata, and registry health. Which lever matters depends on where discovery breaks, which is what continuous measurement shows.
What are Hermoso AI alternatives on ChatGPT?
As of 2026-11-03, Hermoso AI competes with 3RD, 8ight SEO & GEO, Actionbase, AEO/GEO by VibeSEO, Agent Ready, Agent Ready Checker, Agenticaso, AI Interpretation Layer Audit™ and 95 more in ChatGPT AI Search & LLM Visibility (AEO/GEO), ranked by public Discoverability Score.
Where is this profile measured?
This profile uses the geography attached to the latest public registry snapshot: US. Locale tags are intentionally omitted.