Google Search Console integration for Model Context Protocol (MCP)
Hermoso: AI Ad Generator is installed from its publisher's own source and answers where it runs, so this marketplace is not in the path of a single call. There is no address here to send one to, and a panel that pretended otherwise would be showing you an answer we made up. Install it and call it from your own client — the Installation tab has the entry for each one.
What it does
AI ad generator: make image and video ads for your brand and research the ads competitors run.
Quickstart
# 1 — add it to Claude Code
claude mcp add --scope user --transport http hermoso-ai-ad-generator-mcp 'https://app.hermoso.ai/mcp?tools=app-create&src=registry'
# 2 — ask your agent for something these tools do
# enable_tools, find_tools, call_tool and 185 more
Hermoso: AI Ad Generator costs nothing on mcprush: there is no plan to choose here, no cap to set and nothing mcprush can bill you for.
Collected from a public index. Nobody has claimed this account, so nothing here was written by its author — claim it if it is yours.
Where are you running it?
Every route below installs the same thing and ends at the same approval screen. Nothing here runs on your machine and nothing runs on ours: this server answers at its publisher’s own address, and what you install is the connection straight to it.
This is a public server: it answers at its publisher’s own address and this marketplace is not in the path. Claude Code registers it in one command; --scope user makes it available in every project. The transport below is http, as the publisher's own manifest declares it.
claude mcp add --scope user --transport http hermoso-ai-ad-generator-mcp 'https://app.hermoso.ai/mcp?tools=app-create&src=registry'Reconnect, or start a new session, and the tools appear in the model’s tool list.
One config entry pointing straight at the publisher’s own address — this marketplace is not in the path, and nothing from this listing executes on your machine.
188 tools, with what each one reads, writes and reaches shown before you agree — the same list on every route above. Read the tool surface.
Tool surface
What the model actually sees. Descriptions are diffed on every release — see version history.
LIST a group of tools that is not in this session's roster. IT IS NOT HOW YOU REACH A TOOL — call_tool runs any Hermoso tool whether or not it is listed, and that works everywhere. Use this when the session will use MANY tools from one area and you want them in your list. WORKS ON CLIENTS THAT RE-READ THE TOOL LIST (stdio, the CLI, Cursor, Claude Code); a host that fixed its roster at connect time
| Parameter | Type | Required | Notes |
|---|---|---|---|
| groups | array | yes | Groups to switch on, e.g. ['ads']. Unknown names are refused by name rather than ignored. |
Search EVERY Hermoso tool — your starting list is deliberately short, and everything else in the product is here — by name, task or group. Each row gives the tool's PARAMETERS in one line, its CREDIT COST (free means free on every plan; a tool that runs a model quotes the live per-model figure) and its recent HEALTH on this server (failure rate and typical duration, or "no recent calls", which mea
| Parameter | Type | Required | Notes |
|---|---|---|---|
| query | string | no | words from the task or the tool name, e.g. "lead form", "whatsapp", "google ads keyword", "meta insights" |
| group | string | no | limit to one group: core, research, create, channels, channel_admin, analytics, ads, files, workspace |
| limit | number | no | how many to return (default 12, max 40) |
| onlyHealthy | boolean | no | leave out tools that are failing their recent calls or that need a connector this workspace has not made. Default false — nothing is hidden unless you ask, because a missing row reads as a missing cap |
Run ANY Hermoso tool by name — including the paid-campaign, analytics and channel-admin tools that are not in this session's starting list — with the same permissions, the same account and the same result as calling it directly. Get the exact name and its args from find_tools first. This is the route on hosts that cannot reload their tool list mid-conversation (claude.ai, ChatGPT): enable_tool
| Parameter | Type | Required | Notes |
|---|---|---|---|
| name | string | yes | name |
| args | object | no | the tool's arguments as an object, exactly as its own schema takes them |
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
No parameter schema on file.
Return the credit balance, recent priced calls with their credits, and costModel (the one-sentence rule of what costs credits). THE RULE: only AI model runs and Ad Spy research spend credits; publishing, scheduling, ads management, analytics, comments, DMs and connectors cost no credits on any plan (X is the single per-call exception; xCost gives its exact credits per post, with and without a link
No parameter schema on file.
Out of credits? Top up with a credit PACK. Call with no argument to list the available packs (id · credits · price). If the account has a saved card and you have billing-admin rights, calling with pack quotes the exact charge and calling again with confirm:true AND the quote's quote_token charges the saved card instantly (same one-click top-up as the app — no redirect). If there's no saved card
| Parameter | Type | Required | Notes |
|---|---|---|---|
| pack | string | no | the pack id to buy (e.g. pack-2k) — omit to list the available packs first |
| confirm | boolean | no | set true to actually charge the saved card for pack (required for the one-click charge; ignored on the checkout-link path) |
| quote_token | string | no | the quoteToken returned by the quote step — REQUIRED (with confirm:true) to charge; it binds the exact pack + price you quoted (10-minute validity) and makes a retried confirm idempotent |
Report a bug in Hermoso to the team. Use this when something in Hermoso genuinely misbehaves — a tool errors unexpectedly, returns a wrong or malformed result, a render comes back broken, or documented behaviour doesn't match what happened. Include what you were trying to do, the exact tool call and arguments, and what came back. Do NOT use it for out-of-credits, a policy refusal, or a missing cap
| Parameter | Type | Required | Notes |
|---|---|---|---|
| summary | string | yes | one-line summary of the bug |
| details | string | yes | what you were doing, the tool + arguments you called, what you expected, and what actually happened (paste the exact error) |
| severity | string | no | high = blocks the task or loses paid work; medium = wrong output but workable; low = cosmetic |
Ask the Hermoso team for a capability that doesn't exist yet. Use this when you need something Hermoso genuinely can't do — an unsupported platform or channel, a missing model, an export format, a tool that would have completed the user's task but isn't available. Say what the user was trying to achieve, not just the feature name — the use case is what gets built. Free, no credits.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| summary | string | yes | one line: the capability you need |
| details | string | yes | what the user was actually trying to achieve, why the current tools couldn't do it, and what you'd expect the capability to do |
Show this account's billing at a glance: current plan (quote plan.priceUsd per plan.period), credit balance, whether auto-reload is on, whether a card is on file, and whether YOU (this key) have ADMIN rights to change billing. Read-only, free. Call it before upgrade_plan / set_auto_reload to know what's possible — members have read-only billing. IN A SHARED TEAM WORKSPACE A MEMBER SEES THE PLAN AN
No parameter schema on file.
Change the SUBSCRIPTION plan (admin only). No argument lists the monthly plans. Basic ($9.99) is the posting plan: unlimited posting and Autopilot with NO monthly credits (it includes the same 250+ free credits as the Free plan, monthly credits start on Starter, and Autopilot spends credits). Starter, Pro and Studio include monthly credits and no markup on models. Every paid plan posts without a l
| Parameter | Type | Required | Notes |
|---|---|---|---|
| plan | string | no | the plan id to move to (e.g. pro) — omit to list the available plans first |
| period | string | no | monthly only ('yr' buys monthly) |
| confirm | boolean | no | true (with quote_token) to make a quoted upgrade |
| quote_token | string | no | the quoteToken from the quote step |
Turn automatic credit reloads on or off (admin only): when the balance drops below a threshold, the card on file is charged for a top-up pack — SERVER-SIDE, even with no app open. Requires a saved card, added once in the app at first checkout/top-up; if there's none the tool tells you exactly where to add it. After that one-time card setup, agents can manage auto-reload, top-ups and plan links ful
| Parameter | Type | Required | Notes |
|---|---|---|---|
| enabled | boolean | yes | true to turn auto-reload on, false to turn it off |
| thresholdCredits | integer | no | reload when the balance drops below this many credits |
| reloadCredits | integer | no | how many credits to add each reload — must match a credit pack size (see buy_credits) |
List every profile on this account (id + name; a profile is a brand, a creator or a personal workspace) and which one this connection acts on, PLUS any profile another account shared with you. Call this, then use_brand to switch; a SHARED profile switches the same way (pass its name or the profile id printed here). If a profile looks empty when the app shows it full, you are acting on a different
No parameter schema on file.
Pin which profile this connection acts on. Pass the profile id or exact name from list_brands. Works for your own profiles AND one another account SHARED with you (pass its name or the profile id list_brands prints); access is verified against your real membership before it is pinned. Persists for this API key until changed, on every surface (hosted connector included — no environment variables, n
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | yes | profile id (e.g. default / p_xxx), its exact name from list_brands, or the profile id of one shared with you |
Add a NEW profile (a brand, client, creator or personal workspace) and switch to it. Each has its OWN brand details, memory, swipefile, Library, avatars, skills, playbooks and connectors; nothing leaks between them. Use draft_brand to FILL it and update_brand to edit it. Re-running with the same name returns the existing profile instead of a duplicate. Creating it is free. Drafting a NEW brand int
| Parameter | Type | Required | Notes |
|---|---|---|---|
| name | string | yes | the name for the new profile (a brand, client or creator name) |
| activate | boolean | no | switch this connection to the new profile (default true); everything you do next scopes to it |
PERMANENTLY delete a profile and EVERYTHING in it — brand details, memory, swipefile, Library creations, generated assets, avatars, skills, playbooks, chats — and disconnect its connected accounts. Irreversible, and it applies to everyone the workspace is shared with. Call it WITHOUT confirm first: it reports exactly what that workspace holds. Show the user that inventory verbatim, get an unambigu
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | yes | profile id or exact name from list_brands |
| confirm | boolean | no | REQUIRED true — this destroys the whole profile and cannot be undone |
| confirmName | string | no | the workspace's EXACT name, required when it is not empty — copy it from the inventory this tool returned, after the user has agreed to it |
| confirmConnectors | number | no | the number of connected accounts the inventory reported, required when there is at least one — the user must specifically agree to losing them, because reconnecting each needs a browser and no agent c |
Persist an ARBITRARY user file (image, video, audio, PDF or document, up to 150MB) into Hermoso and get back a durable public URL that EVERY publish, schedule and ad-build tool accepts. Kept in the Library. THIS IS THE BRING-YOUR-OWN-CREATIVE PATH: it is for files that have NOTHING to do with a Hermoso render (media on the user's desktop, an agency's finished ad, a photo they shot), and it means y
| Parameter | Type | Required | Notes |
|---|---|---|---|
| getUploadUrl | boolean | no | ASK FOR A ONE-TIME UPLOAD URL instead of uploading now — use this whenever the file is on the user’s machine and you can run a shell or an HTTP request. Returns a uploadUrl you PUT the raw bytes to (a |
| url | string | no | a PUBLIC http(s) URL Hermoso fetches server-side (private/internal addresses are refused, and every redirect hop is re-checked). Works on every surface including the hosted connector, and the bytes ne |
| dataUri | string | no | base64 data: URI of the file bytes (data:<mime>;base64,<…>) — bytes travel over this connection, so keep it small |
| name | string | no | original file name — helps pick the right extension |
Save a Hermoso render — or ANY file — into the user’s connected Google Drive. Pass a Hermoso render URL as url (or urls[] for several); for a local/external file, call upload_file first and pass the url it returns. Optional folder (created if new) + name. Returns the Drive file(s) with a webViewLink. Needs Google Drive connected (Settings > Connectors > Google Drive — one connection covers Drive,
| Parameter | Type | Required | Notes |
|---|---|---|---|
| url | string | no | a single Hermoso render URL to save |
| urls | array | no | several render URLs (up to 20) to save in one call |
| folder | string | no | Drive folder name to save into (created if new) |
| name | string | no | file name (single save) |
Pull the files in a Google Drive or OneDrive FOLDER into this profile's Library, so they can be used like anything rendered here — published, scheduled, cloned, used as a product photo or a reference. Hermoso downloads each file with the user's own connected account (a Drive/OneDrive file is not public) and stores a durable Hermoso url for each. Give folderId from list_drive_files / list_onedriv
| Parameter | Type | Required | Notes |
|---|---|---|---|
| provider | string | yes | which cloud — drive is Google Drive, onedrive is Microsoft OneDrive |
| folderId | string | no | the folder to import, from list_drive_files / list_onedrive_files (onlyFolders:true). Omit for the root of the drive. |
| limit | number | no | how many files to bring across in this call (default 10, max 25). Anything over the limit is listed as skipped so you know what is left. |
List the Google Drive files & folders Hermoso can reach: the ones it created plus any the user picked with the Google file picker in the app (drive.file, never their whole Drive). This is how you find a picked file's id. Filter by query (name contains …), folderId, or onlyFolders:true; paginate with pageToken. Read-only. A FOLDER: picking a folder hands over the folder alone, not its files, so a f
| Parameter | Type | Required | Notes |
|---|---|---|---|
| query | string | no | only files whose name contains this |
| folderId | string | no | list the contents of this folder id |
| recursive | boolean | no | with folderId: the whole tree |
| onlyFolders | boolean | no | list folders only |
| pageSize | number | no | rows per page (1–200, default 50) |
| pageToken | string | no | cursor from a previous call |
| includeTrashed | boolean | no | include trashed files (default false) |
Fetch one Drive file’s metadata — name, type, size, modified time, a webViewLink to open it and a webContentLink to download it. Pass fileId (from list_drive_files). Read-only.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| fileId | string | yes | the Drive file id (from list_drive_files) |
Update a Drive file: rename (name), move it into a folder (moveToFolderId, optionally removeFromFolderId to move OUT of the old one), or trash / untrash it (trash:true|false). Pass fileId (from list_drive_files). To delete permanently, use delete_drive_file.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| fileId | string | yes | the Drive file id |
| name | string | no | new name |
| moveToFolderId | string | no | folder id to move the file into (from create_drive_folder / list_drive_files) |
| removeFromFolderId | string | no | the old parent folder id to remove (when moving) |
| trash | boolean | no | true -> move to Trash; false -> restore from Trash |
Delete a Drive file. By default it goes to Trash (recoverable); pass permanent:true to delete it forever. Pass fileId (from list_drive_files) + confirm:true. Irreversible when permanent — confirm with the user first.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| fileId | string | yes | the Drive file id |
| permanent | boolean | no | true = delete forever; default trashes (recoverable) |
| confirm | boolean | no | REQUIRED true |
Create a folder in the user’s Google Drive (optionally nested under parentId) to organize saved files. Returns the folder id + webViewLink. Use that ID as update_drive_file’s moveToFolderId or as parentId for a nested folder. NOTE: save_to_drive’s folder is a NAME, not this id — it find-or-creates a folder by that name, so pass the folder NAME there (or omit and just save, then move with update_
| Parameter | Type | Required | Notes |
|---|---|---|---|
| name | string | yes | folder name |
| parentId | string | no | parent folder id for a nested folder (default: Drive root) |
Create a new Google Spreadsheet in the user’s Drive and optionally fill it with rows — e.g. export a swipefile, ad list, or performance report. Pass rows as an array of row arrays (first row = headers). Returns the spreadsheet id + URL. Needs Google Drive connected (Settings > Connectors > Google Drive — one connection covers Drive, Sheets and Docs).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| title | string | no | spreadsheet title |
| rows | array | no | rows to write — array of row arrays; first row = headers |
Append rows to a Google Sheet Hermoso can reach — one it created (pass the spreadsheetId from create_sheet) or one the user handed over with the Google file picker in the app (find its id with list_drive_files). rows = array of row arrays.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| spreadsheetId | string | yes | the spreadsheet id from create_sheet |
| rows | array | yes | rows to append — array of row arrays |
| range | string | no | range to append at (default A1 / first sheet) |
Read cells from a Google Sheet Hermoso can reach — one it created, or one the user handed over with the Google file picker in the app (that is how an EXISTING spreadsheet becomes readable; find its id with list_drive_files). Pass the spreadsheetId (from create_sheet) OR paste a Google Sheets URL as sheetUrl. If Google answers that the file was not found, the user has not picked it yet — ask them t
| Parameter | Type | Required | Notes |
|---|---|---|---|
| spreadsheetId | string | no | the spreadsheet id (from create_sheet) |
| sheetUrl | string | no | a Google Sheets URL to read — the spreadsheet id is extracted from it |
| range | string | no | A1 range, e.g. "A1:D50" (default A1:Z1000) |
Turn a SWIPEFILE COLLECTION into a real Google Slides deck — one slide per saved ad, carrying the creative, the brand, the ad copy, the run dates with the run length, and the platform. This is the thing a marketer actually presents to a client or a team. Returns the presentation id + URL. Creates a NEW deck every time: under the drive.file scope Hermoso can only touch files it created, so it canno
| Parameter | Type | Required | Notes |
|---|---|---|---|
| collection | string | no | the swipefile collection to export, by name or id (default: the first collection) |
| title | string | no | deck title (default: the collection name) |
| limit | number | no | max ads to include, 1-60 (default 30) |
Find the Notion pages and databases the user shared with Hermoso, by title, newest edited first. Gives the page id that save_to_notion, append_to_notion_page and read_notion_page take, or the database id to save rows into. Read-only, free. Needs Notion connected (Settings > Connectors > Notion).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| query | string | no | words in the title |
| type | string | no | limit results to pages or to databases; omit for both |
| limit | number | no | 1-100, default 25 |
| cursor | string | no | from the previous page |
Save to the user's Notion as a NEW page: a research report, competitor teardown or campaign report as content (markdown: headings, lists, links, and an image or video URL on its own line becomes the media), a swipefile collection (swipefileCollection: name or id; each ad with its creative, copy, run dates and link), the content calendar (includeSchedule:true), or a mix. Put it under parentPageId
| Parameter | Type | Required | Notes |
|---|---|---|---|
| title | string | no | title of the new Notion page |
| content | string | no | markdown body |
| swipefileCollection | string | no | swipefile collection name or id |
| includeSchedule | boolean | no | add the scheduled posts |
| parentPageId | string | no | page id or link to save under |
| databaseId | string | no | database id or link: saves as a new row |
| dataSourceId | string | no | data source id to save into as a new row; pass only one of parentPageId, databaseId, dataSourceId |
| icon | string | no | one emoji |
Add to the END of an existing Notion page (pageId or its link, from list_notion_pages): markdown content, a swipefileCollection, or includeSchedule:true. Nothing already on the page changes. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| pageId | string | yes | page id or link |
| content | string | no | markdown to add |
| swipefileCollection | string | no | swipefile collection name or id to append, with each ad's creative, copy, dates and link |
| includeSchedule | boolean | no | true appends the scheduled posts too |
Read a Notion page the user shared with Hermoso, as markdown (pageId or a Notion link). A database id answers with its data sources instead. Read-only, free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| pageId | string | yes | page id or Notion link |
Export the posting schedule to a Notion database, one row per post: date, channels, caption, media link and status. Creates a new database (under parentPageId, or private in the workspace) or writes into an existing one (databaseId), matching its columns by name. historyDays adds posts already published. Up to 100 rows per run; the answer says if more were left. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| parentPageId | string | no | Notion page to create the new database under; omit for a private page in the workspace |
| databaseId | string | no | an existing database id or link |
| dataSourceId | string | no | data source id of an existing database to write rows into, matched to columns by name |
| title | string | no | name for a new database |
| historyDays | number | no | 0-90 days of published posts to include |
Read the ROWS of a Notion database the user shared with Hermoso — e.g. the team's content calendar: each row's title, id, link and every property as a plain value (status, date, channels, caption, links), plus the database's columns with their types and options. Filter with match {"Status": "Scheduled"} (typed from the database's own columns) or a raw Notion filter object; sort with sortBy (a colu
| Parameter | Type | Required | Notes |
|---|---|---|---|
| databaseId | string | no | database id or link (list_notion_pages type:"database") |
| dataSourceId | string | no | data source id from an earlier read, as an alternative to databaseId |
| match | object | no | {"Column": value} — equality for status/select/checkbox/number/date, contains for text/multi-select |
| filter | object | no | a raw Notion filter object |
| sortBy | string | no | column name to sort by, or created_time or last_edited_time |
| direction | string | no | ascending (default) or descending |
| limit | number | no | 1-100, default 50 |
| cursor | string | no | nextCursor from the previous page of rows |
| archived | boolean | no | read archived rows instead |
Change properties on ONE row of a Notion database (pageId from query_notion_database) — e.g. mark a calendar row Status "Posted" and put the live post link in its URL column. properties is {"Column": value} by column name: text, number, checkbox, select (an existing option or a new one), status (must be an existing option), multi-select (array; replaces the whole value), date (ISO, or {start, end}
| Parameter | Type | Required | Notes |
|---|---|---|---|
| pageId | string | yes | the row id or link |
| properties | object | no | {"Column": value} |
| trash | boolean | no | true moves the row to Notion trash (needs confirm); false restores it |
| confirm | boolean | no | true confirms moving the row to trash; not needed for other changes |
Create a new Google Doc in the user’s Drive with a title + optional body text — e.g. export ad copy, a creative brief, or a report. Returns the document id + URL. Needs Google Drive connected (Settings > Connectors > Google Drive — one connection covers Drive, Sheets and Docs).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| title | string | no | document title |
| text | string | no | body text to insert |
Append text to the end of a Google Doc Hermoso can reach — one it created (pass the documentId from create_doc) or one the user handed over with the Google file picker in the app (find its id with list_drive_files). Optional dropdown:{title, options:[2-50], selected} appends a Google Docs dropdown chip after the text — e.g. text "Status: " + dropdown {title:"Status", options:["Draft","In review"
| Parameter | Type | Required | Notes |
|---|---|---|---|
| documentId | string | yes | the document id from create_doc |
| text | string | no | text to append at the end of the doc (optional when a dropdown is passed) |
| dropdown | object | no | a dropdown chip to append after the text |
Read the text of a Google Doc Hermoso can reach — one it created, or one the user handed over with the Google file picker in the app (that is how an EXISTING doc becomes readable; find its id with list_drive_files). Pass documentId (from create_doc) OR paste a Google Docs URL as docUrl. Under the drive.file scope it reaches nothing else in the user’s Drive; if Google answers that the file was not
| Parameter | Type | Required | Notes |
|---|---|---|---|
| documentId | string | no | the document id (from create_doc) |
| docUrl | string | no | a Google Docs URL to read — the document id is extracted from it |
The tabs in a Google Spreadsheet, each with its name, numeric sheetId, row/column count and position. Call this BEFORE naming a tab in update_sheet / clear_sheet_range / manage_sheet_tabs / format_sheet, and before proposing to delete one — it is how you learn what the file actually contains instead of guessing at a name. Read-only, free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| spreadsheetId | string | no | the spreadsheet id (from create_sheet, or list_drive_files for one the user picked) |
| sheetUrl | string | no | a Google Sheets URL — the id is extracted from it |
CORRECT cells in a Google Sheet — write values to an exact range, overwriting whatever is there. This is the fix append_to_sheet cannot make: appending only ever adds rows at the bottom, so without this a wrong number stays wrong forever and the only "correction" is a second row contradicting the first. Pass range (e.g. "B2:C5", or "Q3 Report!B2" to name a tab — list_sheet_tabs gives the names)
| Parameter | Type | Required | Notes |
|---|---|---|---|
| spreadsheetId | string | no | Google spreadsheet id; use this or sheetUrl |
| sheetUrl | string | no | Google Sheets link, instead of spreadsheetId |
| range | string | no | A1 range or anchor cell, e.g. "B2:C5", "B2", or "Q3 Report!B2" (default A1) |
| values | array | no | array of row arrays to write |
| updates | array | no | write SEVERAL disjoint ranges in one call, instead of range+values |
| valueInputOption | string | no | USER_ENTERED (default) parses formulas, dates and numbers the way typing them would; RAW stores every value as literal text |
| confirm | boolean | no | required only when the target range already holds values |
Empty a range of cells in a Google Sheet, leaving the rows themselves in place. DESTRUCTIVE: call it WITHOUT confirm first and nothing is cleared — you get back the real number of filled cells in that exact range. Show the user that number, get an unambiguous yes, then call again with confirm:true AND confirmCells set to it. The echo is not ceremony: it is what catches naming A1:Z1000 when you mea
| Parameter | Type | Required | Notes |
|---|---|---|---|
| spreadsheetId | string | no | Google spreadsheet id; use this or sheetUrl |
| sheetUrl | string | no | Google Sheets link, instead of spreadsheetId |
| range | string | yes | the range to clear, e.g. "A2:D50" or "Sheet1!A2:D50" |
| confirm | boolean | no | true clears the range; call without it first to get the filled-cell count |
| confirmCells | number | no | echo back the filled-cell count the unconfirmed call reported |
Add, rename or delete a tab in a Google Spreadsheet. action:"add" + title · action:"rename" + tab + newTitle · action:"delete" + tab. Name the tab by its TITLE or its numeric sheetId (list_sheet_tabs gives both); an unknown tab is refused with the real list rather than a Google error nobody can map back. DELETING a tab destroys everything on it: call it without confirm first to get the filled-cell
| Parameter | Type | Required | Notes |
|---|---|---|---|
| spreadsheetId | string | no | Google spreadsheet id; use this or sheetUrl |
| sheetUrl | string | no | Google Sheets link, instead of spreadsheetId |
| action | string | yes | add, rename or delete a tab |
| tab | string | no | which tab — its title or numeric sheetId (rename / delete) |
| title | string | no | the name for the new tab (action:"add") |
| newTitle | string | no | what to rename the tab to (action:"rename") |
| confirm | boolean | no | true deletes a tab that holds data; needs confirmCells |
| confirmCells | number | no | the filled-cell count an unconfirmed delete reported |
Make an exported sheet readable: bold the header row, FREEZE it so it stays visible while scrolling, and auto-size the columns so nothing is cut off. Worth calling right after create_sheet — a raw export with unsized columns and a header that scrolls away is the difference between a spreadsheet someone reads and one they close. Changes no cell VALUE, so it is never gated. The defaults do all three
| Parameter | Type | Required | Notes |
|---|---|---|---|
| spreadsheetId | string | no | Google spreadsheet id; use this or sheetUrl |
| sheetUrl | string | no | Google Sheets link, instead of spreadsheetId |
| tab | string | no | tab title or numeric sheetId (default: the first tab) |
| boldHeader | boolean | no | bold the header row; default true, false skips it |
| freezeRows | number | no | how many top rows to freeze (default 1, 0 = none) |
| autoResize | boolean | no | auto-size columns to fit their content; default true, false skips it |
EDIT a Google Doc — the correction append_to_doc cannot make. Two shapes: replacements:[{find, replace}] rewrites specific text wherever it appears (call read_doc first and match the text EXACTLY; matchCase:false ignores case), or rewrite:"…" replaces the ENTIRE body (rewrite:"" empties it), or dropdowns:[{title, value}] sets a dropdown chip (e.g. Status -> Approved — read_doc lists every ch
| Parameter | Type | Required | Notes |
|---|---|---|---|
| documentId | string | no | the document id (from create_doc, or list_drive_files for one the user picked) |
| docUrl | string | no | a Google Docs URL — the id is extracted from it |
| replacements | array | no | find/replace pairs, applied in order |
| rewrite | string | no | replace the WHOLE body with this text ("" empties the doc) |
| confirm | boolean | no | true runs a whole-body rewrite after the character count is shown |
| confirmCells | number | no | echo back the character count the unconfirmed call reported (rewrite only) |
| dropdowns | array | no | dropdown chips to set, or dropdowns to change |
Save a Hermoso render — or ANY file — into the user’s connected Microsoft OneDrive. Pass a Hermoso render URL as url (or urls[] for several); for a local/external file, call upload_file first and pass the url it returns. Optional folder (created if new) + name. Returns the OneDrive file(s) with a webViewLink. Needs OneDrive connected (Settings > Connectors > OneDrive).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| url | string | no | a single Hermoso render URL to save |
| urls | array | no | several render URLs (up to 20) to save in one call |
| folder | string | no | OneDrive folder name to save into (created if new) |
| name | string | no | file name (single save) |
List files & folders in the user’s OneDrive — the root by default, a folder’s contents (folderId), or a name search (query). onlyFolders:true lists folders only. Paginate with pageToken (the cursor from a previous call). Read-only.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| query | string | no | search — only items whose name matches this |
| folderId | string | no | list the contents of this folder id |
| onlyFolders | boolean | no | list folders only |
| pageSize | number | no | rows per page (1–200, default 50) |
| pageToken | string | no | cursor from a previous call |
Fetch one OneDrive item’s metadata — name, type, size, modified time, a webViewLink to open it and a webContentLink to download it. Pass fileId (from list_onedrive_files). Read-only.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| fileId | string | yes | the OneDrive item id (from list_onedrive_files) |
Update a OneDrive item: rename (name) and/or move it into a folder (moveToFolderId). Pass fileId (from list_onedrive_files). To remove an item, use delete_onedrive_file.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| fileId | string | yes | the OneDrive item id |
| name | string | no | new name |
| moveToFolderId | string | no | folder id to move the item into (from create_onedrive_folder / list_onedrive_files) |
Delete a OneDrive item — it moves to the OneDrive recycle bin (recoverable there). Pass fileId (from list_onedrive_files) + confirm:true. Confirm the exact file with the user first.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| fileId | string | yes | the OneDrive item id |
| confirm | boolean | no | REQUIRED true |
Create a SHARING LINK to a OneDrive file or folder (fileId from list_onedrive_files): type view (read-only, default), edit, or embed (an <iframe> link, personal OneDrive only); scope anonymous (anyone with the link, default), organization (work or school OneDrive only) or users (only people you invite); optional expiresAt (date or ISO) and password (on a personal OneDrive both need a Microsoft 365
| Parameter | Type | Required | Notes |
|---|---|---|---|
| fileId | string | yes | OneDrive file or folder id from list_onedrive_files |
| type | string | no | view (default), edit, or embed (personal OneDrive only) |
| scope | string | no | anonymous (default, anyone with the link), organization (work or school), or users (invited people only) |
| expiresAt | string | no | e.g. 2026-11-30 or 2026-11-30T17:00:00Z |
| password | string | no | personal OneDrive with Microsoft 365 |
| confirm | boolean | no | required for an anonymous edit link |
action list (default): every sharing link and person with access to a OneDrive file or folder: link URL, view or edit, scope, expiry, password, whether it is inherited from a parent folder. action remove: revoke one (permissionId from the list); anyone relying on it loses access at once and the link cannot be restored, so WITHOUT confirm:true nothing is removed and you get what would go. An inheri
| Parameter | Type | Required | Notes |
|---|---|---|---|
| fileId | string | yes | OneDrive file or folder id |
| action | string | no | list (default) or remove |
| permissionId | string | no | for remove |
| confirm | boolean | no | required true for remove |
Turn a file already in the user’s OneDrive into a PDF or a JPG — Microsoft does the conversion on its own servers, so nothing is re-encoded here and nothing is lost in a screenshot. It reads about 130 source formats, which is the point: PowerPoint and Word decks, Excel, Photoshop PSD, Illustrator AI, Sketch, 3D (fbx/glb/obj), video (mp4/mov/webm), HEIC from an iPhone, and the raw camera formats (C
| Parameter | Type | Required | Notes |
|---|---|---|---|
| fileId | string | yes | the OneDrive item id, from list_onedrive_files |
| format | string | no | default pdf |
| width | number | no | REQUIRED for jpg — output width in pixels |
| height | number | no | REQUIRED for jpg — output height in pixels |
Create a folder in the user’s OneDrive (optionally nested under parentId) to organize saved files. Returns the folder id + webViewLink. Use that id as update_onedrive_file’s moveToFolderId or as parentId for a nested folder. NOTE: save_to_onedrive’s folder is a NAME (find-or-created), not this id.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| name | string | yes | folder name |
| parentId | string | no | parent folder id for a nested folder (default: OneDrive root) |
Plan an ad: turns a brand and a product or brief into an ad concept, with copy variants (headline, primary text, call to action) and either an image prompt or a timed video storyboard, plus the recipe and the model ids to render with. It renders nothing; pass its output to render_ad, generate_image or generate_video. When the user names a length ("a 30 second ad", "make it 45s"), pass it as durati
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | no | brand name, or a brand profile object {name,domain,category,palette,products,…}. OMIT to use the workspace’s SAVED brand + memory automatically (see get_brand); use draft_brand to onboard a new one | |
| product | string | yes | what to advertise + any angle/offer the user specified |
| format | string | no | 'image', 'video', or 'auto' when unspecified |
| durationSeconds | number | no | For video: the total length the user asked for, in seconds (30 for "a 30 second ad"). The storyboard’s scenes add up to it and the script is sized for it. Supported range 4 to 180 seconds; a value out |
| hook | string | no | force the VISUAL scroll-stop mechanic the opening beat is built on — a hook id from list_hooks (e.g. "direct_callout", "mid_problem", "macro_asmr"). Omit to let the planner pick. A hook that cannot be |
| setting | string | no | force the WHERE — a setting id from list_hooks (e.g. "kitchen", "gym", or a surreal one like "volcano_rim" / "airplane_wing", which are played 100% straight and never acknowledged). Omit for a neutral |
| recipe | string | no | a recipe id from hermoso_capabilities to force an archetype |
| reference | string | no | a reference to clone: an ad-library link (Facebook Ad Library, LinkedIn Ad Library, Google Ads Transparency — its real copy/advertiser are fetched) OR a VIDEO link — a TikTok, Instagram Reel, Facebook |
| language | string | no | output language for the ad copy (e.g. Spanish) — default English |
| talent | string | no | who is on camera; omit to follow the format. Say 'someone new' in product to skip reusing a saved creator. |
| concepts | array | no | several variants: one line per concept (a different angle each); built exactly |
| draft | object | no | Only after a video refusal that offered a lighter draft: the {model, durationSeconds} that refusal named. The plan is then written to that length and priced on that model. Use only the values the refu |
| inserts | array | no | VIDEO ONLY: the user's own SCREENS cut in at these seconds (each item's look, kind, start and end say how), the voice running underneath. How many: at most 8, about one per 2.5 s of film (6 s: 2, 10 s |
Remake a video you like FOR THIS BRAND from its link — a TikTok, Instagram Reel, Facebook video or reel, X post, or a direct video file URL. YouTube does not let us download videos, so a YouTube link is refused up front and nothing is charged; the user downloads it themselves and uploads the file (upload_file). Hermoso WATCHES it first (frames across the whole clip plus a transcript of the voiceov
| Parameter | Type | Required | Notes |
|---|---|---|---|
| url | string | yes | the video to clone — a TikTok / Instagram Reel / Facebook / X link, or a direct https video file URL (not YouTube, which cannot be downloaded); a URL, not a local file path; use upload_file for local |
| product | string | no | what the new ad sells, plus any angle or offer; omit to use the saved brand's product |
| changes | string | no | what to change or keep from the original, in the user's words (e.g. "same hook but in a gym", "keep the jump cut, older creator") |
| brand | no | brand name or profile object; OMIT to use the workspace's saved brand (see get_brand) | |
| durationSeconds | number | no | override the length in seconds; omit to match the original |
| language | string | no | language for the new ad's script and copy — default English |
Paste a product page link (Shopify, Amazon or any store page) and get a creator-style video ad planned for that product. Hermoso reads the page (name, photos, what it says about the product, price), saves the product to the brand's catalog, checks the page's real product photo and pins it to this ad (the brand's saved default photo is not changed), then plans the ad. A store that hides its page fr
| Parameter | Type | Required | Notes |
|---|---|---|---|
| url | string | yes | the product page link, https://… |
| style | string | no | ugc_selfie (talking to camera, default), ugc_unboxing, ugc_demo (how-to), ugc_tryon (try-on or haul), ugc_product (product close-ups, no face), ugc_shopping (in-store find), ugc_delivery, transformati |
| durationSeconds | number | no | length in seconds, 4-180; default 15 |
| say | string | no | what the creator should say, or the angle, in the user's words |
| language | string | no | language for the script and copy, default English |
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. The saved brand’s product photo is used automatically (see get_brand / list_product_photos), so never ask the user to
| Parameter | Type | Required | Notes |
|---|---|---|---|
| prompt | string | no | REQUIRED on every model EXCEPT the pose rows below. the full image prompt — subject, composition, lighting, and any on-image ad text. ON A POSE MODEL (product-in-hand / try-on) THIS IS EXTRA DIRECTION |
| refImages | array | no | local file paths or URLs of product/logo references to composite in. ON THE POSE MODELS — any row hermoso_capabilities marks needsRefs with a refsMax, such as putting your product in someone’s han |
| useBrand | boolean | no | default true: with no refImages, the server hydrates the SAVED brand’s product/logo references so the output lands on-brand; pass false for a pure prompt-only render |
| raw | boolean | no | RAW MODEL ACCESS: run the caller’s prompt on the named model with no Hermoso adjustments at all — the prompt reaches the provider byte-identical (no hex-to-colour-name rewrite, no prepended fidelity p |
| aspectRatio | string | no | e.g. '1:1', '9:16', '16:9', '4:5'. Each model draws its own list (hermoso_capabilities prints it per model, e.g. Nano Banana 2 goes to 1:8 and 8:1); a ratio the chosen model cannot draw is refused bef |
| model | string | no | image model id from hermoso_capabilities. A model whose refs.mode is "edit" there (gpt-image-2.6, gpt-image-2.5) takes your refImages on ITS OWN editor, up to its refs.max, instead of the default |
| job | string | no | what the picture is for. With no model named it renders on the engine judged best for that job (hermoso_capabilities jobEngines.image lists them): static_ad (the default when neither model nor ` |
| format | string | no | the file to make (default png). gif = an ANIMATED GIF, a short loop with no sound (describe the subject, the motion and the loop); svg = an editable VECTOR (logo, icon, flat illustration). Both run on |
| frames | integer | no | format gif only: frames in the loop, 2 to 16 (default 9) |
| frameMs | integer | no | format gif only: milliseconds per frame, 20 to 2000, rounded to 10 (default 200; 9 frames x 200 = a 1.8 s loop) |
| complexity | string | no | gpt-image-2.6 / 2.6-flare only: how much the model plans before drawing (default high, best for dense text and complex layouts; low suits a small focused change). No separate charge |
| imageSize | string | no | pixel-size preset for models that support it: 1K/2K, and 4K on the models hermoso_capabilities lists with a 4K imageSize price (a 4K ask on any other model is refused, free) — omit for the default |
| fixLabel | boolean | no | default true: when the saved brand's product photo rides in this render, the product's label on the finished image is READ and compared with the photo, and re-printed from the photo at close range ONL |
| logoPlacement | string | no | overlay: real logo file laid flat. in_scene: the scene leaves a blank patch on the object and the real file is printed onto it (perspective, light). Never drawn by the model. auto (default): from the |
| mask | string | no | MASKED EDIT — change ONE region and keep the rest: a mask (path or URL, any size) for refImages[0]. TRANSPARENT = change, or on a mask with no transparency WHITE = change, black = keep; the reply says |
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
| Parameter | Type | Required | Notes |
|---|---|---|---|
| image | string | yes | the image to edit: URL, Library item URL, upload_file URL or local path |
| instruction | string | yes | the change to make, in plain words (pass the user’s own words for a removal or plain photo edit) |
| removal | boolean | no | true when the edit REMOVES text, branding, a logo, a watermark, a person or an object, so the brand name and logo are not re-added |
| logo | boolean | no | — |
| mask | string | no | optional mask image (URL or local path) marking the region to change: transparent = change, or white = change on an opaque mask |
| markup | string | no | MARKUP-GUIDED EDIT: drawn annotations (circles, arrows, scribbles) showing what to change, as a URL or local path: a transparent PNG of just the marks, or a copy of the same image with the marks drawn |
| model | string | no | the editor, only when the user names one: gpt-image-2.6, gpt-image-2.6-flare (its own faster editor) or gpt-image-2.5. Omit for the default |
| complexity | string | no | gpt-image-2.6 editors only: how much it plans before drawing (default high; low suits a small focused change) |
| dryRun | boolean | no | true = return what this edit usually costs in credits and render nothing |
| fixLabel | boolean | no | false = leave the product label exactly as rendered: no product lookup, no label check, no re-print (default on when the ad shows the brand’s saved product) |
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);
| Parameter | Type | Required | Notes |
|---|---|---|---|
| image | string | yes | the finished static ad: URL, Library item URL, upload_file URL or local path |
| headlines | array | no | your own headlines to test (up to 10); omit to have them written |
| count | integer | no | how many headlines to write when headlines is omitted (default 5) |
| brief | string | no | what to test, e.g. "price-led vs outcome-led" or "speak to busy parents" |
| fixLabel | boolean | no | false = leave the product label exactly as rendered: no product lookup, no label check, no re-print (default on when the ad shows the brand’s saved product) |
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 ca
| Parameter | Type | Required | Notes |
|---|---|---|---|
| image | string | yes | the finished static ad: URL, Library item URL, upload_file URL or local path |
| aspectRatios | array | no | target canvases (default 1:1, 4:5, 9:16) |
| fixLabel | boolean | no | false = leave the product label exactly as rendered: no product lookup, no label check, no re-print (default on when the ad shows the brand’s saved product) |
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
| Parameter | Type | Required | Notes |
|---|---|---|---|
| image | string | yes | the finished static ad: URL, Library item URL, upload_file URL or local path |
| languages | array | yes | target languages, by name |
| fixLabel | boolean | no | false = leave the product label exactly as rendered: no product lookup, no label check, no re-print (default on when the ad shows the brand’s saved product) |
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
| Parameter | Type | Required | Notes |
|---|---|---|---|
| framework | string | no | concept framework id (default 'posed_portrait') — before_after · social_ui · three_step · screenshot · posed_portrait · posed_action · specific_day · graphical · landscape · map_aerial · product · add |
| frameworkRequested | boolean | no | true ONLY when the USER named this framework — it is what authorizes a text-carrying framework (social_ui / news_clip / specific_day / map_aerial) to bake its short UI label |
| sceneBrief | string | no | what the thumbnail depicts — the concept in one dense sentence, rendered exactly |
| topic | string | no | the video's topic — used to pick the hero object when you don't name keyElements |
| headline | string | no | 2–4 word headline. Typeset OVER the finished render by default (free, always legible); newlines split it into stacked lines |
| headlineLines | array | no | explicit headline lines (up to 3) — overrides splitting headline on newlines |
| bakeText | boolean | no | default false. true paints the headline INTO the generation — only on an explicit user ask; it leaks garbled text elsewhere in the frame |
| bakedUiText | string | no | short label for a text-carrying framework (a chat bubble, a DAY N badge, a news lower-third, a map callout) — needs frameworkRequested:true |
| overlayStyle | string | no | headline style: beast (default), fire, neon-lime, clean-glass, marker, or your own CSS declarations |
| font | string | no | headline font: Anton (default) or any Google Fonts family |
| headlinePlace | string | no | bottom (default), top, center, or a 0-1 fraction from the top; never over the face |
| faceImages | array | no | up to 3 face photos (URLs or local paths) — each becomes a locked CHARACTER identity, in order |
| people | array | no | people described in prose instead of by photo (each still gets the chosen expression) |
| castGenericPerson | boolean | no | pass true only after the user has explicitly chosen a generated stranger over their own face |
| emotion | string | no | the expression on the face (default 'shock') — shock · hype · fear · confusion · determination · smug · charisma · disgust · awe · rage · laugh, or your own phrase |
| emotions | array | no | render one variant per emotion (variants = emotions × takes, max 16) |
| takes | number | no | camera takes per emotion, 1–4: designed framing / low-angle hero / extreme close-up / wide dutch tilt |
| variants | number | no | how many thumbnails to render (default 1, max 16). Each is its own billed render — offer a set of 4 rather than assuming |
| aspectRatio | string | no | '16:9' (YouTube, default) / '9:16' (Shorts) / '4:5' (Instagram) / '4:3' / '1:1' |
| keyElements | string | no | signature props / effects that make it pop — oversized, flying toward camera |
RAW speech as an MP3: one voice reading a script (text), or a DIALOGUE of two or more people (lines), each in their own voice. Not for adding audio to a video (render_ad and generate_video voice their own spots; change_voice re-voices a finished clip). ONE VOICE: text + engine (default 'seed-audio'; also 'eleven-v4', 'minimax-speech', 'kokoro'…) + voice, a preset from that engine (hermoso_capabili
| Parameter | Type | Required | Notes |
|---|---|---|---|
| text | string | no | ONE VOICE: the script to speak (≤900 characters) |
| engine | string | no | voice-engine id: one voice defaults to 'seed-audio'; a dialogue defaults to the dialogue model ('eleven-dialogue'); or 'eleven-v4', 'minimax-speech', 'kokoro'… (hermoso_capabilities) |
| voice | string | no | ONE VOICE: a preset from the chosen engine (e.g. 'Aria'/'George' on eleven-v4, 'stokie_en' on seed-audio); omit for the engine default |
| lines | array | no | DIALOGUE: the turns in speaking order |
| dryRun | boolean | no | true = the exact credits only, nothing made or charged |
MAKE a sound effect or ambience that does not exist yet, from a description, as an MP3: a whoosh, a door slam, a cash-register ding, rain on a window, a busy cafe. (find_sound FINDS an existing sound, a meme or trending TikTok sound; this MAKES one.) seconds = length, 0.5-30 (default 5); loop:true for a loopable bed. engine: omit for Seed Audio 1.0 (length and loop are asked for in words, so the l
| Parameter | Type | Required | Notes |
|---|---|---|---|
| describe | string | yes | the sound in words: what makes it, where, how it moves, e.g. 'a fast whoosh past the ear' or 'a busy cafe, cups and low murmur' |
| seconds | number | no | length in seconds, 0.5-30 (default 5) |
| loop | boolean | no | true for a loopable bed |
| engine | string | no | omit for 'seed-audio' (Seed Audio 1.0), or 'eleven-sfx' (ElevenLabs Sound Effects v3) |
| dryRun | boolean | no | true = the exact credits only, nothing made or charged |
RAW music: describe it (genre, mood, instruments, tempo), get an instrumental MP3. No length: a full track at a flat fee (explainerMusicCredits in hermoso_capabilities). With seconds: by the second up to about 38 s. A named model always runs.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| prompt | string | yes | the music in words, e.g. 'lo-fi jazz, brushed drums, 80 bpm' |
| seconds | number | no | track length, up to 300 |
| model | string | no | minimax-music-3 or lyria-3.5 |
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 an
| Parameter | Type | Required | Notes |
|---|---|---|---|
| prompt | string | yes | the writing task / question |
| raw | boolean | no | RAW MODEL ACCESS: send the prompt with NO Hermoso system prompt — the model answers as itself rather than as an ad copywriter. Use it whenever the ask is not marketing copy (analysis, code, extraction |
| model | string | no | a writing-model id from hermoso_capabilities (a Claude / Gemini / GPT / Llama / DeepSeek id) — omit for the default |
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
| Parameter | Type | Required | Notes |
|---|---|---|---|
| creative | object | yes | the FULL structured output of plan_ad (must contain video_storyboard) |
| creator | string | no | CAST A SAVED CREATOR in this ad — their id from list_creators, or the name you know them by (“Sarah”), or a PRESET AI creator from list_creators presets by exact name or id (free, no generation). Thei |
| faceRoute | string | no | 'face_lane' records the user's statement "I own the rights to this face" for the real person in their reference (paid plans; the likeness terms apply). Set it only when the user picked that option aft |
| model | string | no | video model id from hermoso_capabilities (default: the engine the router picks for the plan: seedance-2.5 for every ad, a person talking to camera included). Naming one is a DELIBERATE pick — the serv |
| durationSeconds | number | no | total ad length in seconds, 4–180 (clamped). Omit to keep the plan’s own length, almost always right: this only RE-TIMES the authored board; to change the ad’s length, re-run plan_ad with durationSeco |
| aspectRatio | string | no | output aspect ratio, e.g. 9:16 (default) / 1:1 / 16:9 |
| resolution | string | no | Omit = the default tier, hermoso_capabilities defaultResolution (720p), the tier its credits prices are for. 1080p is ~2.25x, 4k more, 480p less: quote creditsByRes and pass the tier you quoted. A t |
| captions | boolean | no | burn the plan's per-scene on-screen words as caption pills. DEFAULT FALSE on every recipe — set true ONLY when the user asks for on-screen text or captions; no recipe turns them on by itself |
| endCard | boolean | no | append the branded end card. DEFAULT FALSE on every recipe — set true ONLY when the user asks for an end card (a clone of a video that had none should not grow one) |
| music | boolean/string | no | music bed: OFF unless asked. true = the plan's own music line; or the bed in words ('lo-fi jazz, brushed drums'); false = none |
| musicModel | string | no | compose the bed on minimax-music-3 or lyria-3.5, skipping the free library |
| board | boolean | no | paint the storyboard board first (default: on for UGC and cinematic spots, off for a product-only commercial); false = render from the text shot list |
| foundations | string | no | product commercial / cinematic: 'choose' paints ONLY the foundations (identity sheet + four-up moodboard, or hero + location) and returns them, no video — then call again with moodboardPick + foundati |
| moodboardPick | integer | no | which moodboard panel (1-4, left to right, top to bottom) the storyboard follows; default 1 |
| foundationImages | object | no | images a foundations:'choose' call returned, reused as-is (not repainted) |
| lockup | boolean | no | brand wordmark + tagline composited over the closing seconds. DEFAULT FALSE — set true ONLY when the user asks for branding on the close |
| textStyle | no | THE LOOK of captions and the end card, only with captions or endCard and only when the user described one: the look in WORDS ("chunky yellow comic letters, purple outline"), a preset (editorial: big s | |
| ttsVoice | string | no | voiceover voice name (e.g. Rachel / George) when the plan voices over |
| dryRun | boolean | no | return the routing decision (single pass vs stitched acts, resolved model + act lengths) and the estimated credits the real render reserves, WITHOUT submitting a render — free, nothing charged |
| allowGenericProduct | boolean | no | proceed even though this brand has NO product photo on file and the ad features a product — the packaging will be INVENTED. Only pass true after telling the user that and hearing they are fine with a |
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, billed for its compute only, ready to post. Presets are SHORTCUTS; 'custom' is YOUR OWN design as config.html (+ css), so no layout, type, c
| Parameter | Type | Required | Notes |
|---|---|---|---|
| config | object | yes | MUST include config.template: 'custom' or a preset id, plus its fields; language ('Portuguese') for its own words. PRESETS: 'slideshow' (IMAGES, TikTok photo mode / Reels 1080x1920, or size:'4:5' feed |
MOTION GRAPHICS and ANIMATED VIDEO in one call: kinetic typography (animated text), a logo animation or intro, a product launch film, an animated social cut. MOVING PICTURES are the default for launch, product and social: in each scene with a picture, one thing physically happens (a seed splits open, an eye opens, the view travels through a forest), filmed by an image-to-video model, with the type
| Parameter | Type | Required | Notes |
|---|---|---|---|
| recipe | string | no | type, logo, launch, product or social; help:true lists the recipes |
| hook | string | no | opening line; '*word*' = italic accent |
| lines | array | no | more short lines, in order |
| tagline | string | no | line shown under the headline or logo |
| name | string | no | default: the saved brand's |
| logo | string | no | logo image: https, upload_file URL or Library id; default the brand's |
| images | array | no | pictures, hero first (https, upload_file URL or Library id) |
| size | string | no | frame shape: 16:9, 9:16, 1:1 or 4:5 |
| seconds | number | no | logo 4-10, others 6-60 |
| mood | string | no | music and pace |
| music | string | no | 'off' = silent |
| theme | string | no | logo: dark for a light logo |
| energy | number | no | how much the film keeps moving, 0 to 1; 0 films exactly what is written |
| moving | boolean | no | true: one thing physically happens in each picture, filmed by a video model. false: stills moved by a virtual camera, far cheaper. Default true for launch, product, social |
| moves | array | no | per scene, what happens in its picture; omit and the planner writes them |
| brief | string | no | what the film is about, for the planner |
| model | string | no | image-to-video model id from hermoso_capabilities; default is the best value for short shots |
| resolution | string | no | the moving shots: 720p or 1080p (default) where the model has them |
| spec | object | no | the whole film as JSON, from a quote or a saved film; help:true shows the spec language |
| projectUrl | string | no | a past film's url; returns its saved spec to edit and render again |
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, billed for its compute only. 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
| Parameter | Type | Required | Notes |
|---|---|---|---|
| videoUrl | string | yes | the served URL of the video to finish (from a previous render/job); a URL, not a local file path; use upload_file for local files |
| header | string | no | header pill copy, ≤40 chars (required when pills is on) |
| sub | string | no | accent sub-pill copy, ≤34 chars (usually the product/brand) |
| points | array | no | 3-4 proof points, ≤44 chars each |
| accent | string | no | brand accent hex for the sub-pill |
| pills | boolean | no | default true — set false for a grain-only pass |
| grain | boolean | no | default false — anti-AI film-grain finish |
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 attribut
| Parameter | Type | Required | Notes |
|---|---|---|---|
| videoUrl | string | yes | the video to edit: a render / Library URL, a direct file, or a public post link; a URL, not a local file path; use upload_file for local files |
| ops | array | yes | the ordered edit plan (max 6 ops) |
| brandName | string | no | override the workspace brand name |
| domain | string | no | override the brand website |
| accent | string | no | override the brand accent hex |
| dryRun | boolean | no | true = return the estimated credits this edit reserves and run nothing |
Compose ANY edit or transition yourself; there is no preset list. Segments on an output timeline (later ones drawn on top; overlapping ones ARE the transition), each animated by keyframes, plus your own HTML overlays and masks, compiled server-side into one render. Whip pan, zoom through, push, spin, speed ramp, slow motion, freeze frame, reverse, flash, J-cut / L-cut, circle or shape wipe, split
| Parameter | Type | Required | Notes |
|---|---|---|---|
| videoUrl | string | no | the video 'this' refers to (optional when every segment names its own src) |
| segments | array | yes | the clips on the output, max 12, later ones on top. Every animated field (scale x y rotate opacity exposure blur mblur brightness contrast saturation, audio.gain) is a constant or keyframes [{t: secon |
| overlays | array | no | HTML/CSS overlays animated with @keyframes, each shown between its start and end |
| sfx | array | no | sound effects placed on the output timeline |
| size | string | no | canvas: '9:16' default, '1:1', '4:5', '16:9', any 'W:H' (1:3 to 3:1, on a 1080 short side) or exact 'WxH' pixels |
| fps | number | no | the frame rate, a whole number from 24 to 60 (24, 25, 30, 60 are the usual ones). Leave it out: the canvas takes the rate of its own footage (the rate of most of it on screen), so no frames are invent |
| motionBlur | boolean | no | default true: anything that moves gets a real shutter blur along its path |
| review | boolean | no | default true: a vision read of each seam (about 2 credits) comes back with the render, with the seam frames |
| match | no | every cut: 'auto' default | |
| reframe | no | every cut: 'auto' default |
LOOK at a video's exact frames, free: one contact-sheet image of the frames from start to end at fps (e.g. a seam, 3.0-3.8 s at 10 fps) or at the listed times, tiles in reading order with each tile's second listed. Use it to find a moment before an edit and to check a render's seam after it. Takes a Library / render URL, a direct file or a public post link (a TikTok / Reel / Facebook / X post link
| Parameter | Type | Required | Notes |
|---|---|---|---|
| url | string | yes | the video |
| start | number | no | window start in seconds |
| end | number | no | window end in seconds |
| fps | number | no | frames per second in the window (default ~16 frames across it) |
| times | array | no | exact seconds instead of a window (max 48; max 8 with save) |
| save | boolean | no | true = save the frames at times / clips as full-size stills and return their URLs |
| clips | array | no | save: one still from the middle of each numbered clip (split at hard cuts; negative counts from the end) |
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 s
| Parameter | Type | Required | Notes |
|---|---|---|---|
| videoUrl | string | no | the served URL of the master video to fix (not needed with job) |
| startSeconds | number | no | window start in seconds (not needed with job) |
| endSeconds | number | no | window end in seconds (window 1.5-8s; not needed with job) |
| prompt | string | no | what the replacement footage should show — describe the shot, matching the master's style. With job: the change to that block's picture, in words |
| job | string | no | a finished video-blocks film (explainer / faceless channel): its job id. Edits one block of it instead of splicing a window |
| block | number | no | with job: which block, 1-based (block 3 = 20-30s of a film of 10s blocks) |
| atSeconds | number | no | with job: the moment in the film instead of block ("the line at 0:12" = 12) |
| line | string | no | with job: the new narration for that block, the exact words to say (said as written, never rewritten; refused for free when it cannot fit the block) |
| refImage | string | no | optional product/style anchor image URL |
| speechWindows | array | no | [[start,end],...] windows with spoken lines — the fix window must not overlap these |
| dryRun | boolean | no | true = return the estimated credits this fix reserves and render nothing |
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 e
| Parameter | Type | Required | Notes |
|---|---|---|---|
| video | string | yes | the long video to clip — a Vimeo/Loom/Dailymotion/Streamable/Rumble/Wistia/Twitch/TED watch URL, a direct https .mp4/.mov/.webm, or a Hermoso /generated/ URL; a URL, not a local file path; use upload_ |
| count | number | no | how many clips to cut, 1-8 (default 4) |
| aspectRatio | string | no | clip shape: '9:16' (default), '1:1', '16:9', any 'W:H', or 'keep' for the source framing |
| captions | boolean | no | burn subtitles into every clip. DEFAULT TRUE — a clip cut from a podcast or a talk is watched on mute, and the words are the product. Set false for clean footage. A clip whose window carries no readab |
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. Only when asked, textStyle 'highlight' (or a highlightColor) lights the word being said, timed word by word. Or pass cues to burn your own lin
| Parameter | Type | Required | Notes |
|---|---|---|---|
| video | string | yes | the video to subtitle; a URL, not a local file path; use upload_file for local files |
| textStyle | no | the look: words, a preset or fields; omit for the default | |
| burn | boolean | no | false = only the .srt |
| auto | string | no | sentences (default) or words: timing unit for transcribed subtitles |
| wordsPerCue | number | no | words per subtitle line when auto is words |
| cues | array | no | your own lines in seconds, no overlap, max 90 chars, no emoji |
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
| Parameter | Type | Required | Notes |
|---|---|---|---|
| topic | string | no | what the explainer should teach or explain — a topic or a short brief (host_episode: or pass script) |
| lane | string | no | 'blocks' (default) = 10-second video blocks, real motion, one narrated line per block; 'stills' = the picture film (a still about every 1.5s, narrated, no video model; cheaper). Quote either with dryR |
| format | string | no | blocks lane: 'explainer' (default; Gemini Omni 720p, 16:9 or 9:16, runs exactly the length asked) or 'faceless_channel' (a YouTube/TikTok faceless channel video; MiniMax H3 at 2K, 16:9 by default, fiv |
| creator | string | no | host_episode: the host, a saved creator or a preset by name or id (list_creators) |
| hostImage | string | no | host_episode: a photo URL of the host instead (upload_file for a local file); a real person's face needs a paid plan |
| script | string | no | host_episode: the exact words, said verbatim and split at natural breaks into 4-30s pieces |
| fromDraft | string | no | host_episode: a finished draft's job id, to render it in HD (720p) with the same script, cameras, set and host |
| setting | string | no | host_episode: the set in words (default: written to fit the topic) |
| cameras | array | no | host_episode: the rotation, ids frontal / three_quarter / close or framings in words; one entry = one fixed camera |
| thumbnail | boolean | no | host_episode: one thumbnail of the host (default true) |
| durationSeconds | number | no | target length 20-120s (default 60); drives the section count — ~10s of narration each, 3-8 sections |
| frameDensity | string | no | how many pictures per second of narration, and therefore what it costs. 'standard' (default) is a frame about every 1.5s — the density a stills film needs to read as a film rather than a slideshow; 'l |
| aspectRatio | string | no | '9:16' default (a faceless channel video and a host episode default to 16:9) |
| style | string | no | visual style: 'cinematic' (default, photoreal); styled shortcuts editorial_collage, flat_vector, stickman, whiteboard, ink_marker, silhouette, storybook, paper_diorama, isometric, claymation, pixel_ar |
| channel | string | no | the CHANNEL TYPE — it sets the pacing, the narration register and the default look, and is orthogonal to style (a named style always wins): explainer (casual second-person, fast cuts), history (witt |
| voice | string | no | narration voice name — omit for the default warm read |
| captions | boolean | no | turn ON-SCREEN TEXT on. DEFAULT FALSE, and leave it false unless the user asks — the narration already says the point and the pictures carry it, so the clean film is the better default. `captions:true |
| subtitles | boolean | no | which on-screen text, once captions is on. LEAVE IT UNSET (or true) for SUBTITLES — every spoken word, in order, timed to the narration; no extra render, and there is NO cue limit, so the whole film |
| music | string | no | music bed under the narration, measured to sit about 14 dB under the voice and sidechain-ducked beneath it. Omit and the KIDS and FAIRYTALE channels get their recommended bed COMPOSED for this film — |
| upscale | number | no | optional FINAL upscale — 2 doubles each side, 4 quadruples. Captions and the end card are burned BEFORE it so they upscale with the frame. It is priced BY LENGTH and it is the expensive part — several |
Finish a video-blocks explainer or faceless-channel film (make_explainer) that STOPPED partway: a block the video model would not render, or a server restart. What it already made is kept (the script, the style key, the narration and every block that rendered), so this renders ONLY the missing blocks and re-assembles the same film, with the same topic, look, voice, length and options. It reserves
| Parameter | Type | Required | Notes |
|---|---|---|---|
| jobId | string | yes | the id of the film that stopped (job_...), as get_job or list_jobs shows it |
| dryRun | boolean | no | true = return the credits the resume reserves and render nothing |
CONTENT FACTORY: turn ONE topic into a SERIES of faceless short videos (narration over pictures, no presenter), optionally in the STYLE of a trending reference video. Give topic ("make a video about…"), episodes (1-10, default 1) or your own episodeTitles, durationSeconds (20-120 per episode, default 30) and optionally reference (a TikTok / Reel / Facebook / X link or a video file URL wh
| Parameter | Type | Required | Notes |
|---|---|---|---|
| topic | string | no | what the series is about, in the user's words |
| episodes | number | no | how many episodes, 1-10 (default 1) |
| episodeTitles | array | no | your own episode titles, in order (sets the episode count) |
| durationSeconds | number | no | length of EACH episode, 20-120 seconds (default 30) |
| reference | string | no | a video link whose style every episode copies |
| aspectRatio | string | no | default: the reference's shape, else 9:16 |
| style | string | no | override the look in words (e.g. "flat 2D cartoon") instead of the reference's |
| captions | boolean | no | burn subtitles of every spoken word. Default: on only when the reference itself burns its spoken words as subtitles, otherwise off; nothing else is ever put on screen (no app screens, URLs, cards or s |
| voice | string | no | narrator: 'female' or 'male' (default follows the reference), or any narration voice name make_explainer takes |
| format | string | no | renderer lane: 'explainer' (default; exact length) or 'faceless_channel' (2K, whole 10s blocks) |
| planOnly | boolean | no | write the episode plan and style card, render nothing (a small planning charge) |
| plan | no | the plan a planOnly call returned, to run it without planning again | |
| moreOf | no | MORE EPISODES: the plan an earlier call returned. Keeps its style and length, writes new episodes only | |
| confirm | boolean | no | REQUIRED true to start rendering (real spend). Without it you get a free quote |
| jobIds | array | no | SCHEDULE MODE: the episode job ids to schedule (finished ones are booked) |
| schedule | object | no | SCHEDULE MODE: where and when the first episode posts; the rest follow every everyHours (default 24) |
| postCaptions | array | no | SCHEDULE MODE: one post caption per episode, in order (default: the episode title) |
ONE-CLICK EFFECTS, the same ones as the /tools pages: a photo goes in, a styled picture comes out, and most can then become a 5-second clip. Plush toy, 3D figure, mascot, comic book, pixel game, billboard ad, giant product, bullet time, packshot, outfit swap, headshot, relight and many more. Name no effect for the free list. effect alone: what it takes. effect + images: a free quote; add c
| Parameter | Type | Required | Notes |
|---|---|---|---|
| effect | string | no | effect id from the list |
| images | array | no | its pictures, in the order listed: URL, local path or Library id |
| options | object | no | its listed options, e.g. {material:"knit"}; "custom" means use describe |
| text | string | no | a line printed exactly, on an effect that takes one |
| describe | string | no | your own words: added to the look, or the custom choice |
| aspectRatio | string | no | one of its listed shapes |
| still | string | no | CLIP STEP: the finished picture to animate |
| speech | string | no | CLIP STEP: a line said in the clip, on an effect that speaks |
| confirm | boolean | no | true = run it (real spend); without it, a free quote |
Render an 18-30s music-led PRODUCT SIZZLE: ONE 15s Seedance 2.5 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 wil
| Parameter | Type | Required | Notes |
|---|---|---|---|
| prompt | string | yes | what the sizzle should show — the product, the setting, the look |
| seconds | number | no | finished length, clamped to 18-30s (default 25). The PAID hero render is always 15s regardless — this only changes how the cuts and cards are packed |
| refImage | string | no | product packshot URL that anchors the real label — strongly recommended |
| aspectRatio | string | no | '9:16' default; anything the Seedance row that renders it does not list falls back to 9:16 |
| resolution | string | no | hero-clip resolution and therefore the whole cost — DEFAULT '1080p' (≈1,040 credits); '720p' ≈470, '480p' ≈220, '4k' ≈4,130 |
| specs | array | no | up to 4 spec lines for the typeset cards, ≤26 chars each |
| cta | string | no | closing CTA line, ≤30 chars |
| brandName | string | no | brand name on the cards — defaults to the workspace brand |
| musicMood | string | no | music-bed mood, e.g. driving / cinematic / upbeat |
| style | string | no | the LOOK of the sizzle (same price for every look): hypermotion (Fast cuts, crash zooms and speed ramps), product_2d (The real product with playful hand-drawn 2D animation around it), typography (Prod |
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),
| Parameter | Type | Required | Notes |
|---|---|---|---|
| dryRun | boolean | no | true = render NOTHING and return the credits this exact clip would hold (an estimate; the final charge is its actual cost). Free. Call this to price a clip before running it: without it the call rende |
| prompt | string | no | the video prompt / shot description (for a refVideo edit, this is the transformation instruction); optional with shots |
| raw | boolean | no | Send the prompt to the model exactly as written, without the wording Hermoso usually adds (packaging and label guidance, reference lines, colour names for hex codes, quality hints). Use it when you wa |
| refImage | string | no | local path or URL to anchor the first frame. 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 a |
| speak | string | no | A PERSON SAYING THESE EXACT WORDS TO CAMERA — the default for any "make my photo talk" / spokesperson / talk-to-camera ask. Pass with creator or a portrait as refImage: the video model films them |
| creator | string | no | STAR A SAVED CREATOR in this clip — their id from list_creators, or the name you know them by, or a PRESET AI creator from list_creators presets (exact name or id; free, no generation). Their saved po |
| angles | boolean | no | OPTIONAL, OFF BY DEFAULT: with creator, also send the extra views that creator already has saved (their pose plates, up to 2) beside their portrait. A test showed no visible improvement over the por |
| refImages | array | no | SEVERAL reference images (paths or URLs: a person, products, a place) that must all appear. A model uses up to its refs.max in hermoso_capabilities (one-image models take the first); with `refs.prom |
| refVideo | string | no | URL of an existing video to EDIT rather than generate from scratch — the clip is transformed per your prompt, keeping the SOURCE clip’s aspect ratio and, on models hermoso_capabilities marks `sourceLe |
| endImage | string | no | local path or URL of the LAST frame: the clip travels from refImage (required with it) to this image. Only models with endFrame true in hermoso_capabilities take it; any other named model is refused b |
| loop | boolean | no | true = a seamless loop whose last frame flows back into its first. Only models with loop true in hermoso_capabilities; needs refImage and cannot be combined with endImage. |
| shots | array | no | MULTI-SHOT: one clip cut into these shots, in order; the seconds add up to a length the model renders. Replaces prompt. Only models with multiShot true in hermoso_capabilities; a shot prompt over th |
| extend | boolean | no | true = EXTEND refVideo by durationSeconds (default 5) with what your prompt says happens next, as ONE clip. By default it CUTS to a NEW SHOT: new angle, same scene, room sound carried, no music at the |
| extendMode | string | no | with extend: 'continue' = the SAME take past its last frame, only when asked seamless (models with extend in hermoso_capabilities). |
| interactionId | string | no | with extend:true on an Omni model: the interactionId returned by an earlier render on that model — continues it from its own stored context instead of re-uploading refVideo. |
| durationSeconds | number | no | length of THIS ONE clip in seconds: one of the CHOSEN model’s listed durations in hermoso_capabilities (they differ per model, 4–8s on the short ones up to 30s). One continuous generation, so a longer |
| aspectRatio | string | no | default '9:16' |
| model | string | no | video model id from hermoso_capabilities; a named model is never swapped without asking. Omit to let the router pick; its default per job (jobEngines in hermoso_capabilities, from watched side-by-side |
| faceRoute | string | no | 'face_lane' records the user's statement "I own the rights to this face" for the real person in their reference (paid plans; the likeness terms apply). Set it only when the user picked that option aft |
| resolution | string | no | Omit = the default tier, hermoso_capabilities defaultResolution (720p), the tier its credits prices are for. 1080p is ~2.25x, 4k more, 480p less: quote creditsByRes and pass the tier you quoted. A t |
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,
| Parameter | Type | Required | Notes |
|---|---|---|---|
| image | string | yes | local path or URL of the presenter portrait. 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 a |
| script | string | yes | the words the avatar speaks |
| voice | string | no | voice name (Aria / Sarah / George / Adam). Leave it out and the voice matches the person in image; if nobody can be read, the call is refused free asking for one |
| engine | string | no | leave out for the standard engine; only an engine listed in hermoso_capabilities avatarEngines is accepted |
| resolution | string | no | '720p' (default) or '480p' |
| prompt | string | no | motion direction, only for an engine other than standard that hermoso_capabilities lists |
| seed | integer | no | fixed seed, only for an engine other than standard |
| acceptQueue | boolean | no | only for an engine hermoso_capabilities marks oneAtATime: wait in line |
| dryRun | boolean | no | return the credits this exact job would hold, without rendering |
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
| Parameter | Type | Required | Notes |
|---|---|---|---|
| scenes | array | yes | array of scene objects (visual + optional voiceover/seconds) |
| aspectRatio | string | no | output aspect ratio, e.g. 9:16 (default) / 1:1 / 16:9 |
| voiceover | string | no | full voiceover script spoken across the scenes |
| voice | string | no | voiceover voice name, e.g. Rachel / George |
| resolution | string | no | 720p (default), 1080p for full detail, or 480p for a cheaper draft |
| model | string | no | video model id from hermoso_capabilities — omit to let the router pick |
| durationSeconds | number | no | total spot length in seconds (defaults to the sum of the scenes’ seconds) |
Poll a render job by id. Returns status (queued|running|done|error), progress, and on done the served media URL. Renders take 1–3 minutes: keep calling this until done/error without asking the user — several calls is normal, not a stall. An id that does not exist on this account answers status "not_found" — that is FINAL: stop polling it, and do not re-fire the render (that double-charges).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| id | string | yes | the job id: starts with job_, from a render tool's jobId or from list_jobs (generate_voice and generate_sfx return their file directly and have no job) |
Use this when the user wants a week of social posts planned, or answers yes or no to planning their week. It drafts one batch of 5 posts for the active brand: a new image, a caption and a suggested day and time for each. Without confirm it returns the price and whether the batch fits the account’s posting allowance, and spends nothing. Confirm:true starts the batch and bills credits; read the post
| Parameter | Type | Required | Notes |
|---|---|---|---|
| confirm | boolean | no | true only after the user agreed to the price |
| decline | boolean | no | true when the user said no |
| timezone | string | no | the user’s IANA timezone when known, e.g. America/New_York; the suggested times use it |
List the bundled Hermoso SKILLS — multi-step workflow instructions (SKILL.md) that orchestrate the other tools (research an ad space, plan+render a finished ad, product photoshoot, raw generation) — plus the in-app strategy skills and creative recipes. Call get_skill to load a bundle. Read-only, free.
No parameter schema on file.
Load a bundled skill’s full SKILL.md workflow instructions by name (from list_skills), or a built-in in-app skill by its id (e.g. youtube_script, retention_skeleton). Follow the loaded instructions to run that workflow with the other tools. Read-only, free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| name | string | yes | bundle name from list_skills, e.g. hermoso-generate |
Save a reusable custom SKILL — a named creative directive/playbook applied to future ads (a hook formula, a UGC recipe, a compliance rule, a named specialist persona like “our founder-story style” or “short-form ad strategist”). Distill an imperative, self-contained directive. Merges into the workspace Skills library (list_skills shows built-ins + your custom skills).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | profile id/name from list_brands, this call only |
| name | string | yes | short skill name, e.g. “Founder-story hook” |
| directive | string | yes | the full instruction the skill applies when used (1–6 sentences, imperative) |
Delete one of the workspace’s CUSTOM skills by id (from list_skills). Built-in skills/recipes can’t be deleted. Minor + re-creatable, so no confirm needed.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | profile id/name from list_brands, this call only |
| id | string | yes | the custom skill id (from list_skills) |
List the durable facts & preferences saved in this workspace’s Memory (what the studio remembers about the brand, audience, taste, and do/don’t rules) — the same Memory the web app shows. These shape every future ad. Read-only, free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| category | string | no | filter to one bucket (Brand/Audience/Taste/Do/Don’t/Preference) |
| limit | number | no | max items (default 50, max 200) |
Save a durable fact or PREFERENCE about the brand, audience, or the user’s creative TASTE (e.g. “audience is first-time homebuyers”, “prefers bold lime accents”, “always captions off”) into the workspace Memory so it shapes FUTURE ads. For lasting things, not one-off requests. Merges into the existing Memory (never overwrites); de-dupes on identical text. NEVER how Hermoso, a tool, a connector or
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | profile id/name from list_brands, this call only |
| text | string | yes | the fact/preference, concise |
| category | string | no | short bucket: Brand, Audience, Taste, Do, Don’t, or Preference (default General) |
Delete a saved Memory item by its id (from list_memory). Records a cross-device delete so it doesn’t come back. Minor + re-creatable (you can remember it again), so no confirm needed.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | profile id/name from list_brands, this call only |
| id | string | yes | the memory item id (from list_memory) |
Clean up and consolidate the workspace Memory: drops entries that are about how Hermoso, a tool, a connector or a platform API behaves (product behaviour, not the brand), drops phone numbers and emails, and merges near-duplicate facts into one sentence each. Call with no argument to get the PROPOSAL (what would be removed and merged, with reasons) — nothing changes. Call again with confirm:true to
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | profile id/name from list_brands, this call only |
| confirm | boolean | no | true to APPLY the proposal; omit to only see it |
List this workspace's SWIPEFILE — the saved-ad research board: every named collection and the ads/creatives kept in it (advertiser, headline, body copy, media URL, platform, when it was saved, and any taste tags). The SAME board the web app's Swipefile tab shows. Use it to answer "what have we saved?", to mine the user's own taste before planning an ad, or to find a reference to remix. Read-only,
| Parameter | Type | Required | Notes |
|---|---|---|---|
| collection | string | no | only list ads in this collection (by name or id) — omit for every collection |
| limit | number | no | max ads to return (default 50, max 500) |
Save one or more ads/creatives to a named SWIPEFILE collection, creating the collection if it does not exist — the headless twin of the heart on every ad card in the web app. 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 ta
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | profile id/name from list_brands, this call only |
| collection | string | yes | the collection name — an existing one, or a new one to create |
| items | array | yes | the ads to save |
Set the OUTREACH STATUS and/or a NOTE on a creator already saved in the swipefile (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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | profile id/name from list_brands, this call only |
| key | string | yes | the saved row's key from list_swipefile, e.g. tiktok:handle |
| status | string | no | outreach status: new, contacted, replied, booked or passed |
| note | string | no | replaces the existing note; pass "" to clear it |
List the PLAYBOOKS saved in this workspace — the reusable strategy cards (winning hooks, angles, formats and the concrete plays to run) kept from teardowns, angle mining and creatives worth repeating. The same Playbooks the web app's Playbooks tab lists. Read one before planning an ad so you re-run what already worked instead of starting cold. Read-only, free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| limit | number | no | max playbooks to return (default 25, max 100) |
| full | boolean | no | true to return every hook/angle/play in the text, not just the headline counts |
Save a reusable PLAYBOOK — the strategy takeaways worth re-running: the hooks that work, the angles, the formats, and the concrete plays. 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 swipef
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | profile id/name from list_brands, this call only |
| name | string | yes | the playbook headline — what it is, in a few words |
| hooks | array | no | the opening hooks worth reusing, verbatim |
| angles | array | no | the persuasion angles ({title, detail}) |
| formats | array | no | the formats/recipes this plays best in (e.g. ugc_selfie, cinematic, static) |
| plays | array | no | the concrete plays to run ({title, detail}) — the actionable half |
| source | string | no | where it came from, e.g. “teardown · Ridge” |
Delete a saved playbook by id (from list_playbooks). Records a cross-device delete so it does not come back on the next sync. Minor + re-creatable, so no confirm needed.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | profile id/name from list_brands, this call only |
| id | string | yes | the playbook id (from list_playbooks) |
List this workspace’s SAVED CREATORS — the reusable on-camera cast (AI creators made here, a person pulled from a social profile, a consented photo upload). Read-only, FREE. Each entry gives the name, the PORTRAIT URL, where the portrait came from and whether a real person’s likeness consent is on file, how many extra pose plates exist, and any chosen or cloned voice. TO PUT ONE IN A FINISHED AD,
| Parameter | Type | Required | Notes |
|---|---|---|---|
| limit | number | no | max creators to return (default 24) |
| gender | string | no | filter the PRESET creators by gender (the saved cast is never filtered) |
Add a portrait to this workspace’s reusable CAST so the SAME person can star in future ads — the headless twin of the app’s + > Pick a creator > save. Pass the portrait’s public url (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
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | profile id/name from list_brands, this call only |
| name | string | yes | what to call this creator (e.g. “Sarah”) — list_creators and the app’s picker match on it |
| image | string | no | REQUIRED except with useAnyway. public https url of the portrait (an existing render’s url, or any public photo). Not a local file path — upload it with upload_file first and save the url that returns |
| source | string | no | "generated" (default) = an AI-made person; "upload" / "social" = a REAL person |
| voice | string | no | a default voice name for this persona (engines + voices are in hermoso_capabilities) |
| poses | array | no | up to 4 extra full-body / angle plates of the SAME person (public urls) — they make a wider shot hold the identity |
| look | string | no | their canonical wardrobe/appearance in words — reused to hold the look steady across ads |
| useAnyway | boolean | no | only for a creator whose saved photo was flagged too unclear to cast (render_ad says so): true casts the current photo as it is, no new image needed |
Remove a saved creator from this workspace’s cast by id (from list_creators). Records a cross-device delete so they don’t reappear on the user’s other devices. It only drops the roster entry — ads already rendered with that person are untouched — and the same portrait can be saved again with save_creator, so no confirm is needed.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | profile id/name from list_brands, this call only |
| id | string | yes | the creator id (from list_creators) |
Patch SPECIFIC fields of the active profile (name, domain, sells, summary, category, audience, positioning, voice, style, goal, logos, brand/product name pronunciations, profileType: explore = kept but unused) WITHOUT overwriting the rest — a read-modify-write on the saved brand. Use for “change our voice to playful”, “we sell to dentists now”. To onboard a brand from scratch, use draft_brand. If
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | profile id/name from list_brands, this call only |
| name | string | no | brand name |
| domain | string | no | website domain |
| sells | string | no | what the brand sells |
| summary | string | no | one-line description |
| category | string | no | product or business category, one line |
| audience | string | no | who the brand sells to, one line |
| positioning | string | no | how the brand is positioned against alternatives, one line |
| voice | string | no | brand voice/tone |
| style | string | no | visual style — palette, typography, aesthetic |
| goal | string | no | current marketing goal |
| pronounce | string | no | how the brand NAME is said aloud, as a simple respelling with the stressed syllable in capitals (e.g. "KOH-dee-ak"). Videos use it as a delivery note beside the spoken line; set it when a render mispr |
| pronunciations | object | no | how PRODUCT names are said aloud, e.g. {"Power Cakes": "POW-er cakes"}. Merged into the saved ones; an empty string removes one. |
| logo | string | no | logo image: URL or local file path to a PNG, JPEG or WebP (no SVG) |
| logoLight | string | no | for dark pictures; "none" removes |
| logoDark | string | no | for light pictures |
| profileType | string | no | website, social, describe or explore; explore keeps the profile but stops using it |
Read one of this workspace’s data stores by key, for visibility into what the app holds — playbooks, swipefile, saved locations, avatars, creations, chats, brand, memory, skills. Read-only, free. Allowed keys: heist.memory.v1, heist.skills.v1, heist.playbooks.v1, heist.avatars.v1, heist.locations.v1, heist.chats.v1, heist.creations.v1, heist.assets.v1, heist.brand.v1, adInspo.swipefile.v1. (The ty
| Parameter | Type | Required | Notes |
|---|---|---|---|
| key | string | yes | the store key to read (one of the allowlisted keys) |
| limit | number | no | max array items to return (default 50) |
Read this account's app settings — the LANGUAGE Hermoso writes ads and copy in (chat replies follow the language the user writes in), the app appearance (theme), and whether the weekly competitor-watch email is on. Same settings as the web app's Settings pane. Read-only, free.
No parameter schema on file.
Change this account's app settings. language = the language EVERY ad, script and plan is written in from now on (chat replies follow the language the user writes in) (say the language in plain English, e.g. "German", "Japanese", "Brazilian Portuguese") — it applies to renders made over MCP as well as in the app. theme = the app's appearance, "dark" or "light". watchEmail = the weekly competitor-wa
| Parameter | Type | Required | Notes |
|---|---|---|---|
| language | string | no | language for generated ads and copy (chat replies follow the language the user writes in) — e.g. "English", "German", "Japanese" |
| theme | string | no | app appearance |
| watchEmail | boolean | no | weekly competitor-watch email on/off |
List the third-party accounts connected to this workspace (Meta, Google Ads, Google Drive/Sheets/Docs, YouTube, LinkedIn, OneDrive, Slack, …) — provider, status and the connected account label — PLUS which providers are available to connect. IT ALSO FLAGS A CONNECTION WHOSE PERMISSIONS ARE OUT OF DATE: a provider writes its granted permission set into the token at consent time, so a connection aut
No parameter schema on file.
Show every identity a connected account can act as, and which ones this PROFILE is currently allowed to use — Facebook Pages + Instagram + Meta ad accounts, Google Ads customers, LinkedIn company Pages (and the personal profile), Pinterest ad accounts, Microsoft Advertising accounts. One person often administers several; only the ticked ones can be posted to or spent from. Call this before set_con
| Parameter | Type | Required | Notes |
|---|---|---|---|
| provider | string | yes | which connector’s accounts to list |
Set WHICH of a connector's accounts this profile is allowed to post to and spend from — Facebook Pages / Instagram / Meta ad accounts, Google Ads customers, LinkedIn company Pages (and the personal profile), Pinterest or Microsoft Advertising ad accounts. Pass ids from list_connector_accounts. This REPLACES the current selection: anything you leave out is un-shared, and an EMPTY list shares nothin
| Parameter | Type | Required | Notes |
|---|---|---|---|
| provider | string | yes | which connector to scope |
| accountIds | array | yes | the ids (from list_connector_accounts) this profile may use — an empty array shares nothing |
Disconnect a third-party account from this workspace (Meta, Google Ads, Google Drive/Sheets/Docs, YouTube, TikTok, LinkedIn, X, Reddit Ads, Pinterest, Google Business, Microsoft Advertising/OneDrive, Slack, …). This always drops the stored credentials, so every tool for that provider stops working immediately and posts/campaigns already published are NOT affected. IT NEVER REVOKES THE GRANT AT THE
| Parameter | Type | Required | Notes |
|---|---|---|---|
| provider | string | yes | provider id exactly as list_connectors reports it, e.g. "meta", "google_ads", "youtube", "linkedin" |
| confirm | boolean | no | REQUIRED true — reconnecting a sign-in account afterwards needs the user's browser; a paste-a-key account is reconnected with connect_connector |
| account | string | no | on a channel with several connected accounts (TikTok, X, YouTube, Threads, Bluesky, Telegram, Pinterest): remove ONLY this account (@handle or id from list_connector_accounts) and keep the others |
Connect an account that links with a KEY rather than a sign-in screen, from here, with no browser: AppLovin Ads, Stripe, Intercom, Mailchimp, Klaviyo, Vercel, ChatGPT Ads, Apple Ads, Bluesky, Telegram, Bing Webmaster Tools, PostHog, Mixpanel, Amplitude, WordPress, Slack, Discord, Webhook. Pass provider and fields in that provider's own field names (listed below, * = required). The key is checked l
| Parameter | Type | Required | Notes |
|---|---|---|---|
| provider | string | yes | the connector id: applovin_ads, stripe, intercom, mailchimp, klaviyo, vercel, openai_ads, apple_ads, bluesky, telegram, bing_webmaster, posthog, mixpanel, amplitude, wordpress, slack, discord, webhook |
| fields | object | no | that provider's own field names and values, e.g. {"apiKey":"…"}; the names for each provider are in the description |
On a connector that several teammates can contribute their OWN account to (see list_connectors — the row reports multiContributor), remove YOURS from this profile: your stored credential is dropped and the accounts you shared stop being shared. Your teammates' accounts on the same connection keep working, and nothing changes at the provider — reconnecting in a browser shares again. Use this instea
| Parameter | Type | Required | Notes |
|---|---|---|---|
| provider | string | yes | provider id exactly as list_connectors reports it, e.g. "linkedin", "tiktok_ads", "meta" |
List the members of the current profile — email, role (admin/member) and status. Read-only, free.
No parameter schema on file.
Invite someone to this profile by email (role: member = read-only on billing, admin = full). This SENDS a real email invite / share link — an account change. Confirm the exact email + role with the user, then call with confirm:true.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| string | yes | the invitee’s email | |
| role | string | no | default member |
| confirm | boolean | no | REQUIRED true — this invites a real person |
Remove a member from this profile by email — they lose access (you can re-invite them later), AND every connection THEY made on this profile is disconnected with them: their own X, LinkedIn, TikTok, YouTube, Pinterest, Threads… connections (on a channel holding several accounts, only the accounts they added), and any extra login or authorization they added. Connections the owner or anyone else mad
| Parameter | Type | Required | Notes |
|---|---|---|---|
| string | yes | the member’s email | |
| confirm | boolean | no | REQUIRED true |
Change a workspace member’s role — admin (full access incl. billing) or member (read-only on billing). A privilege change: confirm the exact person + new role with the user, then call with confirm:true.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| string | yes | the member’s email | |
| role | string | yes | the new role |
| confirm | boolean | no | REQUIRED true |
List the most recent render jobs + how many are currently running, so you can report on or resume in-flight work.
No parameter schema on file.
The errors actually recorded against this workspace, GROUPED by fingerprint — the same failure at the same call site is one row with a hit count and first/last seen, sorted defects-first. Each row says whose side it is: ours (a defect worth fixing), user (a refusal we deliberately authored, e.g. not-connected or out-of-credits), or unknown (a vendor 4xx we cannot attribute — never guessed).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| kind | string | no | 'ours' = a defect; 'user' = a refusal we authored; 'unknown' = we could not tell |
| surface | string | no | where it happened: http (an API route), mcp (an agent tool), agent (the in-app Studio agent), job (an async render/publish), client (a browser crash) |
| since | string | no | ISO timestamp — only groups last seen at or after this |
| limit | number | no | how many groups to return (default 50, max 200) |
One error group in full by fingerprint (from list_errors): every field, plus the most recent redacted occurrences — status, connector, job id, workspace, and a shape-only echo of the inputs. This is what makes a bug reproducible. Read-only, 0 credits.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| fingerprint | string | yes | the fp value from list_errors |
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. Bills the discovery model's token cost (no ad-data charge); free inside a new account's first-brand setup.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| domain | string | yes | the brand domain, e.g. flourish.com |
| mode | string | no | 'competitors' (default, excludes the searched company), 'inspiration' (best relevant ads incl. it), or 'company' |
Use this when the user asks which ads a named brand is running. 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 a
| Parameter | Type | Required | Notes |
|---|---|---|---|
| companyName | string | no | the advertiser name, e.g. "Liquid Death" (company / brand / name are read as this too) |
| domain | string | no | the advertiser domain, e.g. liquiddeath.com — a full website URL works (url / website are read as this too). Pass companyName OR domain |
| company | string | no | same as companyName |
| brand | string | no | same as companyName |
| name | string | no | same as companyName |
| url | string | no | same as domain |
| website | string | no | same as domain |
| country | string | no | 2-letter, default 'US' |
| limit | number | no | max ads per platform (default 30) |
| sort | string | no | 'longest_running' (default) etc. |
Set, change or stop a brand's MARKET RADAR (the standing competitor watch; Ad Spy > Watching in the web app; each brand has its own), run weekly or daily: newAds (what watched brands launched), winners (their ad passing 30 days running), trends + topPosts (formats and most-viewed posts for niche topics), ideas (3 to make). Only what you pass changes. competitors / topics REPLACE their lists (max 8
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | profile id/name from list_brands, this call only |
| competitors | array | no | the COMPLETE brand list |
| topics | array | no | niche search phrases, e.g. "protein breakfast" |
| modules | object | no | on/off; omitted keep theirs |
| cadence | string | no | weekly (default) or daily check |
| boolean | no | the weekly email | |
| auto | boolean | no | true picks competitors, similar brands and topics from the saved brand or seed; bills one model call at its token cost |
| seed | string | no | for auto: a website, brand or niche |
| runNow | boolean | no | check now (spends now) |
| force | boolean | no | with runNow: check again right after a check, allowed once what is watched changed |
| dryRun | boolean | no | true quotes one check and saves nothing |
Read what a brand's MARKET RADAR (set_competitor_watch) has found: each watched brand's new ads, the winners (ads past 30 days running, with a ready brief to make your own version), the trending formats and top posts for its topics, and the ideas to make, plus its state (lastRun, nextRunAt, lastRunOk, paused) and the quote for its next check. Findings marked seed:true are what a brand was alread
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | profile id/name from list_brands, this call only |
| competitor | string | no | only this watched brand's ads (exact name as returned) |
| limit | number | no | max ads (default 25, max 75) |
Use this when the user asks an open-ended ad question across several brands or platforms, such as comparisons, which angles are working in a market, or ad examples for a category. Do not use for one named brand’s current ads: pull_competitor_ads covers that. Open-ended ad research that needs JUDGMENT across platforms — comparisons, "what angle is working", "who else is doing this", anything where
| Parameter | Type | Required | Notes |
|---|---|---|---|
| query | string | yes | what to research, e.g. "the longest-running protein-pancake ads on Meta" |
| brand | no | brand name or profile object to tailor the research to; omit to use the workspace’s saved brand |
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 credits on each ad-library read it makes: one for a keyword; for a brand name
| Parameter | Type | Required | Notes |
|---|---|---|---|
| query | string | no | keyword search across ALL advertisers (use INSTEAD of companyName/pageId) |
| companyName | string | no | one advertiser’s ads by brand name — resolved to the brand’s own page only on evidence (its Instagram, its domain, or ads that land on its domain); pass domain too for the best match |
| domain | string | no | the advertiser’s website, e.g. buffer.com — with companyName, used to confirm which Meta page is really theirs |
| pageId | string | no | one advertiser’s ads by Facebook page id (most precise) |
| country | string | no | 2-letter code or 'ALL' (default ALL) |
| status | string | no | ACTIVE = currently running; default ALL (includes proven past winners) |
| mediaType | string | no | filter by creative type (default ALL) |
| limit | integer | no | max ads returned (1–25, default 8) |
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, l
| Parameter | Type | Required | Notes |
|---|---|---|---|
| domain | string | no | the advertiser's domain, e.g. nike.com |
| advertiserId | string | no | Google advertiser id (AR…) when the domain is ambiguous |
| region | string | no | 2-letter region, default US |
| limit | integer | no | max ads returned (1–25, default 8) |
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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| company | string | no | advertiser company name |
| keyword | string | no | keyword across all advertisers |
| companyId | string | no | LinkedIn company id (numeric) when the name is ambiguous |
| countries | string | no | CSV of 2-letter codes like 'US,CA'; omit or 'ALL' = worldwide |
| limit | integer | no | max ads returned (1–25, default 8) |
Scan organic TikTok, Instagram Reels and YouTube for a niche across a few query variants, fold the posts into creators, and rank them on median views, engagement rate and how often they show up for that niche; the top rows get follower counts AND public contact info (an Instagram business email / phone / category, the bio link, an email in a TikTok bio) so outreach can start from the result. Real
| Parameter | Type | Required | Notes |
|---|---|---|---|
| niche | string | yes | product category, topic or hashtag — "calorie tracker app", "matcha", "#cleanbeauty" |
| platforms | array | no | default all three |
| limit | number | no | creators to return, 1–30 (default 12) |
| queries | number | no | query variants per platform, 1–4 (default 3); each is a paid search call |
| minAvgViews | number | no | minimum average views per post; creators below this floor are dropped |
| minEngagement | number | no | interactions per view, 0–1 (0.05 = 5%) |
| enrich | boolean | no | followers + public contact for the top 3 on TikTok and on Instagram (default true, 1 credit each) |
| marketplace | boolean | no | also search Instagram’s creator marketplace (Meta’s own creator directory, free) for the same niche; needs the Meta connector |
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 pos
| Parameter | Type | Required | Notes |
|---|---|---|---|
| topic | string | yes | subject, brand, product or hashtag — "liquid death", "coffee", "#homecafe" |
| platforms | array | no | default all three |
| limit | number | no | posts per platform, 1–60 (default 24) |
| queries | number | no | query variants per platform, 1–4 (default 1); each is a paid search call |
What to post on TikTok, from posts that are working THIS MONTH in the brand's niche: up to 6 content ideas (hook, format, caption, why, an act-within window of 7d or 30d, a quality tier), EACH backed by linked TikTok posts with their views, likes and engagement. One bounded pull of the last 30 days: up to 4 keyword searches + up to 5 hashtag pulls, then up to 8 reads of what the strongest posts SA
| Parameter | Type | Required | Notes |
|---|---|---|---|
| product | string | no | the product (a name from list_products, or words); omit to use the saved brand |
| category | string | no | the niche in words, e.g. "matcha", "running shoes" |
| market | string | no | 2-letter country the posts are read from (default US) |
| goal | string | no | what the posts should do: sales, followers, awareness… |
| keywords | array | no | your own search words (up to 4), instead of the derived ones |
| hashtags | array | no | your own hashtags (up to 5, # optional) |
| reads | integer | no | how many of the strongest posts to read for their spoken hook, 0-8 (default 4) |
| dryRun | boolean | no | true = price only, nothing looked up or charged |
| pick | object | no | THE PICK: one idea object exactly as this tool returned it in ideas |
| to | string | no | where the pick goes: render_ad (plan_ad then render_ad, the default) or make_series |
| claims | array | no | with pick: extra claims the USER stated about their product (added to the brand facts) |
| brandId | string | no | a profile id/name from list_brands; omit for the active profile |
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; for what to POST, tiktok_ideas. Spends about a credit.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| query | string | yes | keyword or hashtag (no # needed) |
| limit | integer | no | max videos returned (1–25, default 8) |
Organic Instagram REELS keyword search (/v2/instagram/reels/search — our only IG keyword surface; profile/hashtag pulls go through fetch_social_data with a handle). Returns compact JSON {desc, author, handle, plays, likes, link, cover} per reel, ranked by plays. It matches captions worldwide and cannot be limited to a country or language: for one market, search in the words people there use, or re
| Parameter | Type | Required | Notes |
|---|---|---|---|
| query | string | yes | keyword to search reels for |
| limit | integer | no | max reels returned (1–25, default 8) |
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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| query | string | yes | keyword to search videos for |
| limit | integer | no | max videos returned (1–25, default 8) |
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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| query | string | yes | what to search Reddit for |
| limit | integer | no | max posts returned (1–25, default 8) |
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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| query | string | yes | keyword to search Threads for |
| limit | integer | no | max posts returned (1–25, default 8) |
Find a PLACE to tag on an Instagram or Facebook post: a shop, cafe, restaurant, venue or landmark. Give its name with the city ("Blue Bottle Coffee Oakland"), or paste its Facebook Page link, an Instagram place link (instagram.com/explore/locations/...) or its numeric id. A pasted link or id is read and comes back as that Page's taggable pageId with its name and address, or a plain reason it canno
| Parameter | Type | Required | Notes |
|---|---|---|---|
| query | string | yes | the place name with its city (e.g. "Blue Bottle Coffee Oakland"), or a Facebook Page link, an Instagram place link, or a numeric id |
| near | string | no | optional city or area to narrow a name search, e.g. "Paris" |
| limit | integer | no | how many Pages to check on a name search, 1 to 8 (default 5); each is about 1 credit |
Generic escape hatch for any ALLOWLISTED long-tail social/web endpoint the dedicated search_* tools don't cover — e.g. {path:'/v1/instagram/profile', params:{handle:'nike'}}. Allowlisted platform families: TikTok (+ TikTok Shop), Instagram, YouTube, Facebook (organic profiles/posts/events/marketplace), LinkedIn (organic posts/companies), Twitter/X, Reddit, Threads, Snapchat, Pinterest, Twitch, Blu
| Parameter | Type | Required | Notes |
|---|---|---|---|
| path | string | yes | exact endpoint path, e.g. '/v1/tiktok/profile' — non-allowlisted paths are rejected |
| params | object | no | endpoint query params, e.g. {handle:'nike'} |
What Hermoso ALREADY KNOWS for the active profile: the same saved profile (products, logos, palette, positioning) + learned memory the web Studio uses. Call it when you need to know whether a brand is on file: if hasBrand is true you can omit brand everywhere; if false, onboard with draft_brand. Not a required first step before a render: the create tools read the saved brand by themselves. 0 credi
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | profile id/name from list_brands, this call only |
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. 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, V
| Parameter | Type | Required | Notes |
|---|---|---|---|
| domain | string | no | a website to scrape, or a social link (profile, YouTube channel or one post) to draft from the account it names |
| description | string | no | a free-text brand description (no website) |
| socialHandle | string | no | a social handle (creators); needs platform, never guessed |
| platform | string | no | socialHandle platform: instagram/tiktok/youtube/twitter/threads |
| save | boolean | no | save onto the active profile (like Studio onboarding) so plan_ad/create use it automatically. Default: saves only when NO brand is saved yet; pass true to REPLACE the saved profile (the drafted fields |
| confirmed | boolean | no | user confirmed it is their brand: read + save site product photos (paid) |
Browse this workspace's Library — every image/video generated in the Studio, and uploads (source:'upload'), newest first (the same Library the web app shows). Returns served URLs you can open directly or hand to fetch_asset for a download link, plus each asset's id (L0, L1…, which plan_ad and render_ad inserts take), a human TITLE (the ad's headline or concept, the prompt's first words, or the u
| Parameter | Type | Required | Notes |
|---|---|---|---|
| kind | string | no | filter by asset kind (default 'all') |
| uploads | boolean | no | — |
| query | string | no | only assets whose title contains this text (case-insensitive), e.g. an ad headline the user quoted |
| limit | number | no | max assets to return (default 20, max 60) |
PERMANENTLY delete images/videos from this brand's Library: the entry and the stored file. Pass ids (L0… from list_library) or urls. Without confirm it lists what would be deleted; ask the user, then call again with confirm:true. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| ids | array | no | Library ids from list_library |
| urls | array | no | file links |
| confirm | boolean | no | true deletes |
Resolve a generated asset reference (a /generated/… path or any URL) to a clickable absolute URL + a direct download URL.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| url | string | yes | the asset url or /generated/ path |
| name | string | no | optional filename for the download |
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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| url | string | yes | the video URL (a served /generated/ path or a public http(s) video); a URL, not a local file path; use upload_file for local files |
| frames | boolean | no | also return the frames as images |
| words | no | true | 'only': each spoken word's start/end | |
| anchors | array | no | {afterWord|beforeWord|atWord, occurrence?, offset?} -> s |
Turn a VIDEO into WRITTEN pieces: a summary, YouTube chapters (first at 0:00, at least 3, each 10s+), chapter summaries, an X thread (every post under 280 characters), a blog post, timestamped verbatim quotes, or any piece in your own words (pass the words as a format). The video: a YouTube link (read from its captions), a video file URL, a Library id, an upload_file URL or a social post link (rea
| Parameter | Type | Required | Notes |
|---|---|---|---|
| video | string | no | a YouTube link, a video file URL, a Library id (L0…), an upload_file URL or a social post link |
| transcript | string | no | the transcript text, when you already have it; then nothing is read |
| formats | array | no | summary, chapters, chapter_summaries, thread, blog, quotes, or any piece in your own words; default summary |
| instructions | string | no | your direction for every piece: audience, tone, length, a call to action |
| language | string | no | the language to write in, when not English |
| seconds | number | no | the video length in seconds, when you know it: it prices exactly that, and anything past it is not read |
| read | string | no | 'watch' reads a YouTube video by watching it rather than from its captions (one with no captions) |
| model | string | no | a Claude writing-model id from hermoso_capabilities; omit for the default |
| includeTranscript | boolean | no | also return the transcript that was read |
| confirm | boolean | no | true only after the user agreed to the quoted price |
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. A sound that does not exist yet is generate_sfx.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| query | string | no | sound by name, moment or meaning, e.g. 'bad news reaction' |
| url | string | no | link to a TikTok sound, a post, a meme-sound page or an audio file |
| pick | number | no | candidate number from the previous list to take instead of the first |
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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| image | string | no | still picture URL; use this or video |
| video | string | no | video URL to take the picture from, starting at videoStart |
| videoStart | number | no | seconds into the video where the insert starts |
| sound | string | yes | sound URL (find_sound returns one), or a video whose own audio is used |
| soundStart | number | no | seconds into the sound where the insert's sound starts |
| soundEnd | number | no | seconds into the sound where it ends |
| seconds | number | no | fixed insert length, 0.2 to 6 seconds, instead of when the sound lands |
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, own: could another brand run it unchanged), strengths, and the single biggest fix. Use BEFORE spending on distribution, or to rank variants.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| url | string | yes | the ad asset URL (a /generated/ path or public URL) |
| kind | string | no | 'image' (default) or 'video' |
| intent | string | no | what the ad is trying to achieve, for goal-fit scoring |
Check a finished video for named, mechanical issues, ONLY when the user asks (never after every render, and it never re-renders): loudness vs a target (default -14 LUFS) and true peak, black and frozen stretches, captions or text outside the platform-safe band, text running off the frame. Optional assertions about named elements: appearsBy {element, bySec}, before {a, b}, staysInFrame {element}, k
| Parameter | Type | Required | Notes |
|---|---|---|---|
| url | string | yes | the video: a /generated/ path, a public URL or a post link |
| frames | boolean | no | false skips the paid text and element read |
| loudnessTarget | number | no | integrated loudness target in LUFS, default -14 |
| assertions | array | no | what must hold, checked on sampled frames |
Reframe a video to a different aspect ratio (e.g. 16:9 master -> 9:16 vertical): the whole original frame is kept and the new canvas around it is generated, with the sound and timing unchanged. Clips up to 300 seconds. Paid render; returns the served URL of the reframed video. Every placement at once (a free blur-pad by default): repurpose_video. A presenter on any video: add_presenter.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| video | string | yes | the source video URL; a URL, not a local file path; use upload_file for local files |
| aspectRatio | string | yes | the target aspect ratio |
ONE video in, EVERY placement out, in one job, as a named file list (<name>_<placement>_<WxH>[_<lang>].mp4) with a manifest. Placements: 'everywhere' (default: 9:16, 4:5, 1:1, 16:9), any W:H, a pixel size (1200x628) or a platform word (tiktok, reels, stories, feed, square, youtube, linkedin, pinterest). Each is reframed by a BLUR-PAD by default (the whole frame over a blurred copy of itself; free,
| Parameter | Type | Required | Notes |
|---|---|---|---|
| video | string | yes | the video: an https / upload_file URL, a Library id (L0…), or a local path on the CLI |
| placements | array | no | default ['everywhere']; a canvas, a WxH size, a platform word, or {placement, reframe} |
| reframe | string | no | default 'pad' (free); 'ai' is paid and only when the user asks |
| pad | object | no | the pad's look: fill 'blur' (default) or a #rrggbb colour, blur 0-1, dim 0-0.6, position top/center/bottom or 0-1 |
| captions | no | true = word-timed captions on every file (only when the user asks); a textStyle preset name or object sets the look | |
| wordsPerCue | integer | no | captions: 1-8 words a cue (default 4) |
| languages | array | no | dub into these languages (paid), e.g. ['Spanish','de']; the original is kept too |
| name | string | no | the asset name the files are named after |
| dryRun | boolean | no | price only: nothing made, nothing charged |
ONE finished static ad in, VARIANTS × FORMATS out, in one job, as a named file list (<name>_<variant>_<format>_<WxH>.png) with a manifest. The ad's text is read ONCE as the source of truth; each variant's change is made ONCE on the master; every format is derived from that master; every output is text-checked line by line and any line it lost is named (never re-rendered). Variants: {headline}, {la
| Parameter | Type | Required | Notes |
|---|---|---|---|
| image | string | yes | the ad: an https / upload_file URL, a Library id (L0…), or a local path on the CLI |
| formats | array | no | default ['1:1','4:5','9:16','16:9'] |
| variants | array | no | the versions besides the original; a bare string is an edit in words |
| name | string | no | the asset name the files are named after |
| fixLabel | boolean | no | false = no product-label check (default on when the ad shows the brand's saved product) |
| dryRun | boolean | no | price only: nothing made, nothing charged |
Put a PRESENTER on ANY video, only when the user asks for one: their pick of person (creator: a saved or preset creator from list_creators; or image: a photo they gave; never invent one), filmed on a green screen saying the words, cut out and laid over the video in a bottom corner (default), a round badge, or split screen, lip-synced, block by block across the whole video (about 10 s and about
| Parameter | Type | Required | Notes |
|---|---|---|---|
| video | string | yes | the video: an https / upload_file URL, a Library id (L0…), or a local path on the CLI; up to 2 minutes |
| creator | string | no | the presenter: a saved or preset creator, by name or id |
| image | string | no | or a photo of the presenter (a real person's face follows the real-face rules) |
| script | string | no | the exact words to say, used verbatim and split at sentence ends |
| brief | string | no | without a script: what they should talk about |
| look | string | no | — |
| position | string | no | 'br' (default), 'bl', 'tr', 'tl' |
| scale | number | no | 0.2-0.8 of the frame |
| voice | string | no | a voice name, only when asked (default: their own voice) |
| baseAudio | string | no | the video's own sound: 'under' the presenter (default) or 'off' |
| captions | boolean | no | subtitles of what they say; only when asked |
| dryRun | boolean | no | price only: nothing made, nothing charged |
INSERT a newly generated scene into a clip you already have: the video plays up to startTime, the new scene (5 to 13 seconds) plays, then the original picks up again at resumeTime. Use for a product close-up, a reaction, a reveal or a missing beat in the middle of an ad without re-rendering the rest. resumeTime = startTime keeps every second of the original; a later resumeTime also cuts the footag
| Parameter | Type | Required | Notes |
|---|---|---|---|
| video | string | yes | the source video URL (a render, job result or list_library item) |
| startTime | number | yes | seconds into the clip where the new scene starts (1.625-60) |
| resumeTime | number | yes | seconds into the ORIGINAL clip where it picks up again after the new scene (2.625-60, at or after startTime) |
| prompt | string | no | what happens in the new scene: the action, the subject, the camera |
| seconds | number | no | length of the new scene, 5-13 seconds (default 5) |
| refImages | array | no | up to 4 picture URLs of what should appear (the product, a person, a place) |
| refVideos | array | no | up to 1 clip URL whose look or motion the scene follows |
| resolution | string | no | default 720p (rendered at 768p); '480p' is cheaper |
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). Three 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; engine:'detail' rebuilds fine detail such as ha
| Parameter | Type | Required | Notes |
|---|---|---|---|
| video | string | yes | the source video URL; a URL, not a local file path; use upload_file for local files |
| engine | string | no | default 'standard', the precision upscaler. 'flux' = the FLUX 3 video upscaler. 'detail' = rebuilds fine detail, costs the most |
| mode | string | no | FLUX only — 'creative' turns on its detail-enhancement pass; default precise |
FINISH a 480p Seedance 2.5 DRAFT in 1080p: the model regenerates THAT take (same scene, shots, motion, sound) at full resolution. Every 480p Seedance 2.5 render (generate_video / render_ad at resolution 480p) is a draft, finishable for 7 days; its result carries draft {id, expiresAt, finishCredits}. Pass that id, or the draft video URL. It is the full-resolution route for a draft (upscale_video
| Parameter | Type | Required | Notes |
|---|---|---|---|
| draft | string | no | the draft id from the render result (draft.id) |
| video | string | no | or: the URL of the 480p draft video |
| dryRun | boolean | no | true = return the estimated credits and render nothing |
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
| Parameter | Type | Required | Notes |
|---|---|---|---|
| video | string | yes | the source video URL (a render, job result or list_library) |
| style | string | no | a look id from hermoso_capabilities (e.g. claymation, knitted-yarn, cel-shaded-anime-cg) |
| describe | string | no | a look in the user’s own words, when no preset fits, or extra detail on top of a preset |
| styleImages | array | no | pictures that ARE the look (their style, never their subject) |
| characters | array | no | characters to draw the people as. 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 ag |
| engine | string | no | default auto |
| resolution | string | no | Seedance and Wan; default 1080p |
| faceRoute | string | no | 'face_lane' = the user's "I own the rights to this face" for a real person in the source clip (paid plans). Only on the user's say-so, never on your own. |
SWAP AN OBJECT IN A VIDEO ('replace the cup with our can' + the product photo as elements) or 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 name
| Parameter | Type | Required | Notes |
|---|---|---|---|
| video | string | yes | the source video URL (a render, job result or list_library); a URL, not a local file path; use upload_file for local files |
| instruction | string | yes | the change, in the user’s own words |
| keepAudio | boolean | no | default true: keep the source audio; false = silent |
| lighting | string | no | default auto: keep the source light unless the change needs new light (night, a lamp, fire). 'preserve' or 'relight' forces it |
| reference | string | no | OPTIONAL image URL that anchors the MATERIAL of what changes (a fabric, a finish, a colour swatch, the real product). Only its surface is used, never its framing or light |
| literal | boolean | no | true = send the instruction exactly as written, with no preserve/lock wrapper |
| previewFirstFrame | boolean | no | true = edit ONE still of the first frame first and quote the clip; the paid clip does not run |
| previewStill | string | no | the still a previewFirstFrame call returned; the clip then matches the changed thing to it |
| previewAt | number | no | second to preview (default 0); resend with previewStill |
| elements | array | no | OPTIONAL identity/product grounding (≤4): a creator portrait or the real product photo, so the edit restores the REAL thing. Describe each one in the instruction |
| interactionId | string | no | OPTIONAL: the interactionId an earlier Gemini Omni render or edit returned; the edit continues that clip on the same Omni model. If it cannot run, the video editor edits it and the reply says so. |
| engine | string | no | default auto: Seedance 2.5 Edit without a real-looking person, else Kling 4.0 Edit |
| faceRoute | string | no | 'face_lane' = the user's "I own the rights to this face" for a real person in the source clip (paid plans). Only on the user's say-so, never on your own. |
| textChanges | array | no | EVERY change of on-screen words at once ([{from, to}], e.g. each line and its translation). A video laid out from a design is made again with all of them in ONE render, about 2 credits, never a video |
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. HOW: the source video itself DRIVES each variant (motion transfer from one image-edited opening frame)
| Parameter | Type | Required | Notes |
|---|---|---|---|
| video | string | yes | the source video: a file URL, or a TikTok, Reel, Facebook or X post link (not YouTube) |
| images | array | no | up to 9 pictures to put in the variants, in the order @Image1, @Image2 name them: URLs or local paths |
| quality | string | no | 1080p is the default; 720p costs less, and hands holding a product are less reliable at 720p |
| useBrand | boolean | no | false = send no brand product photo (for a video that is not this workspace brand) |
| count | number | no | how many variants, 1-12 (default 6) |
| change | string | no | what should change, in the user's own words, e.g. 'older women, winter streets'; every variant applies it (omit to let the variants vary freely) |
| axes | array | no | what to vary: character, outfit, location, objects (default all four), or your own, e.g. "season" |
| notes | string | no | anything the variants must respect, e.g. "keep it women 25-40", "no gyms" |
| regions | array | no | markets to restyle for, one or more variants each, e.g. ["Berlin","Tokyo","São Paulo"] — visuals only; audio is never translated here |
| quoteOnly | boolean | no | true = the exact credits, free; nothing runs |
| dryRun | boolean | no | true = the paid plan and the price, no render |
| plan | no | the plan a dryRun returned, rendered without planning again |
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 i
| Parameter | Type | Required | Notes |
|---|---|---|---|
| video | string | yes | the finished video to give new hooks: its served file URL |
| count | number | no | how many hook versions, 1-5 (default 3) |
| notes | string | no | anything the hooks must respect, e.g. "keep it calm", "show the product in every hook" |
| hookSeconds | number | no | where the CURRENT hook ends, in seconds (1.5-4). Omit to use the first shot cut. |
| resolution | string | no | render tier for the new opening. Defaults to the source’s OWN tier so the hook matches the rest of the ad; a lower tier costs a lot less and is scaled into the source’s canvas (visibly softer for the |
| productImage | string | no | product photo URL used as a reference in hooks that show the product (defaults to the workspace brand’s first product photo) |
| useBrand | boolean | no | false = send no brand name or product photo (for a video that is not this workspace brand’s) |
| plan | no | the plan object a previous dryRun returned, to render exactly those hooks without planning again | |
| quoteOnly | boolean | no | true = the exact credits, free; nothing runs |
| dryRun | boolean | no | true = the paid plan and the price, no render |
| hooks | array | no | your own openings, one version each |
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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| video | string | yes | the source video URL; a URL, not a local file path; use upload_file for local files |
| language | string | yes | target language, e.g. 'Spanish', 'de', 'French (Canada)' |
| script | string | no | OPTIONAL override for the original spoken words. Leave this out — the source video is transcribed automatically. Only pass it when you already know the exact script and the auto-transcript got it wron |
| voice | string | no | optional, e.g. 'Aria' (woman) or 'George' (man): a native voice of that sex speaks. Default: the source speaker's sex. |
| dryRun | boolean | no | quote only |
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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| video | string | yes | the source video URL; a URL, not a local file path; use upload_file for local files |
| voice | string | no | target narrator voice preset name, e.g. 'Aria', 'George', 'Rachel', 'Sarah', 'Brian', 'Charlotte' (defaults to a warm female read). A saved VOICE CLONE of the user's own voice counts as a preset here |
Copy the motion of any video (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 + ONE photo of a person (saved, real or AI), 4-30s -> seedance (Seedance 2.5 through our face library, 720p: the clip's scene, beats and sound kept
| Parameter | Type | Required | Notes |
|---|---|---|---|
| image | string | no | who performs it: the actor/character image URL (@Image1). Required on kling. No one yet: generate_image a portrait of an AI person from words (no reference photo) and pass its url, or a saved / preset |
| images | array | no | seedance/wan: more pictures, @Image2…: a character, product, outfit or place |
| video | string | yes | the reference video whose motion to re-perform |
| prompt | string | no | optional, e.g. 'same moves, new location: Tokyo at night' |
| engine | string | no | default auto |
| resolution | string | no | a person swap defaults to 720p (seedance or kling-edit); otherwise seedance and wan default 1080p; h3 720p or 1080p, default the clip’s own |
| orientation | string | no | kling only: which aspect to keep: the video's (default) or the image's |
| faceRoute | string | no | 'face_lane' = the user's "I own the rights to this face" for a real person in the source clip (paid plans). Only on the user's say-so, never on your own. |
| dryRun | boolean | no | return the credits this exact job would hold and the engine auto picks, without rendering |
| voice | string | no | a NEW voice for the new performer: 'match' (a stock voice that fits them), 'female'/'male', or Aria, Sarah, Charlotte, George, Brian, Charlie. Omit = the clip's own sound. Never the original speaker's |
| language | string | no | the new voice speaks this language (e.g. 'Spanish'), lip-synced to the new words; voice defaults to 'match' |
| tier | string | no | kling only: 'pro' (default): 1080p and real hand-object interaction. 'standard': about 25% fewer credits and faster, but 720p, and it tends to mime a held object with empty hands. hermoso_capabilities |
RECAST A VIRAL HOOK with the brand's own subject (the egg rolling off the table becomes your product; your host does the viral move), then join it into your clip. voice/language: a new voice, or language lip-synced, for one person swapped in. hook: a public post link (TikTok, Reel, Facebook, X; one lookup; not YouTube, which cannot be downloaded) or a video file. Subject: creator (saved name or id
| Parameter | Type | Required | Notes |
|---|---|---|---|
| hook | string | yes | the viral hook: a post link or a video URL |
| creator | string | no | a saved creator's name or id (list_creators). NONE SAVED: cast a preset by exact name (list_creators), or pass image instead: a portrait of an AI person that generate_image painted from words (no refe |
| image | string | no | a picture of the subject instead: a product or a person. A person generate_image painted from words with no reference photo counts as an AI face on every plan |
| subjects | array | no | a duo or group recast, 2-4 people, instead of creator/image |
| location | string | no | move the scene: a photo URL of the place, or words ("a sunny beach at golden hour") |
| outfit | string | no | what the subject (or, with no subject, the main person in the hook) wears instead: an outfit photo URL or words |
| swap | string | no | what changes, in your own words |
| mode | string | no | auto (always a swap), swap (edit what you describe), or motion (move your person in their own photo's setting) |
| start | number | no | window start in seconds of the hook; minimum window is 3 seconds |
| end | number | no | window end in seconds of the hook |
| prompt | string | no | extra direction for the render |
| model | string | no | swap: omit for the default; a clip editor id (kling-4-edit, kling-o3-edit; hermoso_capabilities), or for one person swapped for another 'wan-3' (≤15 s) / 'minimax-h3-max-recast' |
| faceRoute | string | no | 'face_lane' = the user's "I own the rights to this face" for a real person in the source clip (paid plans). Only on the user's say-so, never on your own. |
| resolution | string | no | swap: the output tier, on an editor that prices by one (kling-4-edit 720p or 1080p); default the hook's own pixels |
| tier | string | no | motion: the motion-transfer tier (default pro) |
| useBrand | boolean | no | false = no brand product photo; true with location/outfit and no subject = also swap in the brand product |
| voice | string | no | one person swapped in: a NEW voice for them instead of the original speaker's: 'match' (fits them), 'female'/'male', or Aria, Sarah, Charlotte, George, Brian, Charlie. Omit = keep the clip's voice. Ne |
| language | string | no | the new voice speaks this language (e.g. 'Spanish'), mouth lip-synced to the new words; voice defaults to 'match' |
| confirm | boolean | no | true = render it (after the user okayed the quote) |
Before any paid render: does a picture or video contain a face, and which video models refuse it (they cannot take a real person's face; a render there fails after it starts) or were measured to take one, plus the model a face-bearing render is routed to. A local detector, no model call, 0 credits. It cannot tell a photo from a drawing, so a drawn face counts. Takes a picture or video URL, a Libra
| Parameter | Type | Required | Notes |
|---|---|---|---|
| url | string | yes | the picture or video to check |
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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | no | brand name or profile object; OMIT to use the workspace’s saved brand | |
| product | string | yes | what to advertise |
| count | integer | no | how many distinct variants (default 6) |
| language | string | no | output language for the variant copy (e.g. Spanish) — default English |
Make a BATCH of finished static image ads for one or several products in one call: each ad a genuinely different selling angle (offer, material or ingredient story, how it is used, a lifestyle moment, an objection, proof the brand really has) AND a different layout (editorial still life, poster headline, packshot on colour, in hand, flat lay, split compare, native phone photo and more; never one l
| Parameter | Type | Required | Notes |
|---|---|---|---|
| products | array | no | product ids or names from list_products, ['all'] for every product with a photo, or omit for the hero product |
| count | integer | no | ads per product, 1-20 (default 4) |
| aspectRatios | array | no | canvases; every ad is rendered once per ratio (default ['4:5']) |
| brief | string | no | what to lean into: a sale the brand is really running, a season, an audience, a tone |
| angles | array | no | the selling angles to spread across, e.g. ['price or offer', 'material story', 'how it is used', 'lifestyle moment']; omit and the planner picks a spread |
| layouts | array | no | your own layout descriptions in words, used slot by slot instead of the built-in layout set |
| language | string | no | language of the on-image copy (default English) |
| model | string | no | image model id from hermoso_capabilities; omit for the default |
| brandId | string | no | a profile id/name from list_brands; omit for the active profile (passing it pins it like use_brand) |
| plan | array | no | the plan (or remaining) a previous call returned: render exactly those ads, no new planning |
| batchId | string | no | the batchId a previous call returned, so the rest lands in the same batch |
| dryRun | boolean | no | true = plan and quote only, nothing rendered |
| logoPlacement | string | no | overlay (default) | in_scene (real file printed onto a blank surface) | none | auto |
Make a marketplace LISTING SET for ONE product in one call (Amazon, Shopify, Etsy, any store page): the MAIN image on pure white (the product alone filling about 85% of the frame, no text, no props, no added logo), then the secondary images a listing carries — a lifestyle shot, a detail close-up with NO infographic, a scale shot that shows its true size, and the product in use — every one rendered
| Parameter | Type | Required | Notes |
|---|---|---|---|
| product | string | no | the product name or id from list_products: its catalog photo and its own facts are used |
| image | string | no | the product photo when it is not in the catalog: an https or upload_file URL, a Library id (L0, L1…) or a local path on the CLI |
| title | string | no | the product name, when it is not in the catalog |
| description | string | no | what the product is, when it is not in the catalog |
| inBox | array | no | what ships in the box (for a whats_in_box shot); nothing else is ever drawn in it |
| shots | array | no | the images, in order: built-in main, lifestyle, detail, scale, in_use, multi_angle, whats_in_box, back_of_pack, or any shot in your own words. Default: main, lifestyle, detail, scale, in_use. main alw |
| aspectRatio | string | no | the canvas for the whole set; default 1:1 (the marketplaces' square) |
| brief | string | no | the seller's direction for the whole set: a mood, a setting, an audience |
| model | string | no | image model id from hermoso_capabilities; omit for the default |
| brandId | string | no | a profile id/name from list_brands; omit for the active profile (passing it pins it like use_brand) |
| aplus | boolean | no | also Amazon's 7 A+ modules: 1 brand hero and 7 brand promise at 21:9 (Premium full-width 1464x600 + mobile 600x450), 2-6 at 3:2 (key benefits, how to use, materials and details, lifestyle, what you ge |
| copy | no | the seller's own words for A+ text fields, by module id then field, e.g. {aplus_hero:{headline:'…'}}; each is clipped to Amazon's limit. Without it the fields are suggested only from the product's own | |
| copyIn | string | no | A+ words: 'fields' (default) = suggested for Amazon's text fields, none painted; 'image' = the words painted on the picture |
| mainImage | string | no | an APPROVED main image (URL or Library id) to draw every other image from instead of rendering a new main |
| mainJob | string | no | the mainJob id a previous call returned, when rendering its remaining: they are drawn from that main |
| plan | array | no | the plan (or remaining) a previous call returned: render exactly those images, no new plan, with the same image |
| dryRun | boolean | no | true = plan and quote only, nothing rendered or charged |
The brand's product catalog: each product's id, title, page link, price (and whether it is current), photos, its own selling points and audience, and when it was last checked against its live page. Free. refresh:'stale' re-reads rows older than a day first, refresh:'all' re-reads every page. Use the ids with make_static_ads.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| refresh | string | no | re-read product pages before listing: 'stale' (older than a day) or 'all' |
| brandId | string | no | a profile id/name from list_brands; omit for the active profile |
Add a product to the brand's catalog from its product-page link: reads the page's own data (name, every photo, price and currency, description, its bullet-point selling points), no model, 0 credits. Re-adding a link updates the row (the live page wins). Without a url, title adds one by hand. audience and sellingPoints you pass are kept through later re-reads.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| url | string | no | the product page link |
| title | string | no | to add a product by hand (no link) |
| images | array | no | photo urls for a hand-added product |
| price | string | no | hand-added only; never printed as current unless read from a page |
| currency | string | no | currency code for the price, e.g. USD |
| description | string | no | product description |
| sellingPoints | array | no | bullet-point selling points, one per item |
| audience | string | no | who this product is for |
| brandId | string | no | a profile id/name from list_brands; omit for the active profile |
Re-read product pages now (all products with a page, or ids) and update the catalog: the live page wins, a failed read is recorded and said. sync:true instead imports the whole store catalog (the brand's connected Shopify store, else its website's public catalog), merging into existing rows. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| ids | array | no | product ids to re-read; omit to re-read every product that has a page |
| sync | boolean | no | import the store catalog instead of re-reading pages |
| brandId | string | no | a profile id/name from list_brands; omit for the active profile |
Edit a catalog product's title, description, selling points, audience or page link. What you set here is kept through later page re-reads. Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| id | string | yes | catalog product id, as add_product returned it |
| title | string | no | product name |
| description | string | no | product description |
| sellingPoints | array | no | selling points; replaces the saved list |
| audience | string | no | who this product is for |
| url | string | no | product page link |
| brandId | string | no | a profile id/name from list_brands; omit for the active profile |
Remove one product from the brand's catalog (its ads already made stay in the Library). Free.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| id | string | yes | catalog product id to remove |
| brandId | string | no | a profile id/name from list_brands; omit for the active profile |
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 thos
| Parameter | Type | Required | Notes |
|---|---|---|---|
| competitor | object | yes | the competitor to tear down |
| ads | array | no | ad objects to tear down (from pull_competitor_ads / search_meta_ads). Omit to auto-pull their Meta ads first. |
| language | string | no | output language (default English) |
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-adjac
| Parameter | Type | Required | Notes |
|---|---|---|---|
| copy | string | yes | the ad copy / script / on-screen text to check |
| claims | string | no | the claims / proof points the ad makes |
| category | string | no | the product category — helps pick the relevant policy pages |
| imageDescription | string | no | a description of the creative / image when relevant |
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'
| Parameter | Type | Required | Notes |
|---|---|---|---|
| imageUrl | string | yes | the URL of the static ad image to clone. 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 c |
| brandId | string | no | a profile id/name from list_brands to clone for; omit to use the active profile |
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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| imageUrl | string | yes | the URL of the static ad image to clone. 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 c |
| brandId | string | no | a profile id/name from list_brands to clone for; omit to use the active profile |
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 YO
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brandId | string | no | a profile id/name from list_brands to mine for; omit to use the active profile |
| reviews | no | your own customer reviews: a list of review texts, or one pasted block (one per line, numbered, blank-line separated, or CSV with a review column) | |
| reviewsUrl | string | no | a URL of your reviews: an uploaded CSV, TXT or JSON file (from upload_file) or a review page. On a local CLI a file path also works. |
| useOwnReviewsOnly | boolean | no | true = mine only your reviews, no public search (no search credits). Default false = merge with public customer language. |
List the product photos ALREADY saved in your workspace — the brand's product library plus any app-store screens (also surfaces photos locked in your OTHER creations, since a set product lands in the shared library). FREE — returns each photo's url + the product it shows (the shop's own title when one is on file). These are already saved: never ask the user to upload them again. Call it before set
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brandId | string | no | a profile id/name from list_brands whose product library to list; omit to use the active profile |
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 phot
| Parameter | Type | Required | Notes |
|---|---|---|---|
| imageUrl | string | yes | the image URL to lock as the product (from a research result, a workspace / list_product_photos url, or any public product photo) |
| source_note | string | no | a short note on where it came from, e.g. "from their IG post" |
| brandId | string | no | a profile id/name from list_brands to lock the product for; omit to use the active profile |
Pull an APP brand's REAL store screenshots (Google Play or App Store) 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'. A store link or package id goes in storeUrl and is read exactly; else pass appName (defaults to the saved
| Parameter | Type | Required | Notes |
|---|---|---|---|
| appName | string | no | the app's name to look up on the App Store — defaults to the saved brand's name |
| storeUrl | string | no | a Google Play / App Store link or package id |
| brandId | string | no | a profile id/name from list_brands to save the screens onto; omit to use the active profile |
List the connected Facebook Page's or Instagram account's OWN existing posts — id, caption, permalink, publish date and format. 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
| Parameter | Type | Required | Notes |
|---|---|---|---|
| tagged | boolean | no | INSTAGRAM — list posts OTHER accounts tagged the brand in (UGC, creator posts, partner mentions), with each author’s @handle, instead of the brand’s own posts. Free. |
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| target | string | no | default facebook; 'instagram' reads the Page's linked IG business account |
| account | string | no | which Instagram account — an @handle or id from list_connector_accounts("instagram"). Needed when several are linked, and the way an Instagram Login (standalone) account is reached; omit for the one P |
| pageId | string | no | which connected Page — omit when the profile has only one |
| limit | number | no | how many posts (default 25, max 100) |
| cursor | string | no | paging cursor returned by a previous call |
| includeUnpublished | boolean | no | Facebook only — also return unpublished drafts (hidden by default) |
List every post Hermoso has recorded publishing for this brand, newest first, across all channels — channel, permalink, caption, format, the HOOK and SUBJECT it was written to, and its measured engagement. The WHOLE history, no cap: pass the reply's nextCursor as cursor for older posts. Each row says how it was recorded: 'captured' (written at publish time — the hook is what the author intended)
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| channel | string | no | filter to one channel: facebook, instagram, threads, x, linkedin, youtube, tiktok, pinterest, bluesky, google_business |
| limit | number | no | max posts (default 50, max 200), newest first |
| cursor | string | no | nextCursor from a previous reply: the next, older page |
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
| Parameter | Type | Required | Notes |
|---|---|---|---|
| channel | string | no | restrict the performance half to one channel (facebook, instagram, threads, x, linkedin, youtube, tiktok, pinterest, bluesky) |
| authentic | boolean | no | true if the planned ad is an authentic/UGC/creator-register render — on-screen-text hooks are then reported unusable, with the reason |
| category | string | no | the product category (e.g. 'skincare serum', 'protein powder', 'sunglasses') — returns the setting our Location x Tier matrix puts that category in, with the reason |
| tier | string | no | product tier, used with category — changes the FINISH of the room, never the room. Default premium. |
Ready-to-run ad BRIEFS by industry: dropshipping, ecommerce, mobile apps, restaurants, real estate and local services (12 trades: plumbers, electricians, hvac, roofers, landscapers, house-cleaners, pest-control, auto-repair, salons, med-spas, dentists, gyms). Compact list by default; pass id for one template in full: its brief (filled with the active profile's name, and the trade's specifics whe
| Parameter | Type | Required | Notes |
|---|---|---|---|
| vertical | string | no | dropshipping | ecommerce | mobile-app | restaurants | real-estate | local-services; omit for all |
| trade | string | no | for local services: plumbers, electricians, hvac, roofers, landscapers, house-cleaners, pest-control, auto-repair, salons, med-spas, dentists or gyms. Fills the trade specifics into the brief |
| id | string | no | one template id (from the list) to return in full, with its brief |
Use this when the user asks which of their social posts did best. Aggregate this brand's published posts to answer WHICH HOOKS AND SUBJECTS WORK. Groups by hook (default), subject, format (recipe), channel, media format or posting hour, reports the engagement RATE within each channel, and ranks the best and worst POSTS in each channel. Describe a post by the creative it carried (what it shows, its
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| axis | string | no | what to group by — default hook; recipe = the format of the creative |
| channel | string | no | restrict to one channel |
| days | number | no | look back N days (1-730) over the whole history; omit for the recent posts only |
WHAT TO FIX NEXT, post by post — and the tool that fixes it. post_performance tells you which hook is AHEAD; this tells you what is WRONG with a given post and where the next edit goes. Under-distributed is A HOOK PROBLEM (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).
| Parameter | Type | Required | Notes |
|---|---|---|---|
| channel | string | no | restrict to one channel: facebook, instagram, threads, x, linkedin, youtube, tiktok, pinterest, bluesky |
| limit | number | no | how many recent posts to diagnose (default 25, max 200). The baseline is always built from EVERY post recorded for the brand, never only these, so a bad month can never become its own definition of no |
| converting | boolean | no | pass false ONLY when the user has told you these posts are getting seen and are not converting — it re-reads the ones that are earning their reach as an offer problem instead of a win. Omit when you d |
Fetch fresh performance numbers for this brand's recorded posts and store them as a time-series. Metrics ACCRUE, so a post is read at ~24 hours and again at ~7 days; this collects whichever readings are due and skips the ones already taken. A channel that cannot report a metric records it as ABSENT with the reason — never as zero — and a read that fails is recorded as 'could not tell', which contr
| Parameter | Type | Required | Notes |
|---|---|---|---|
| brand | string | no | WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CA |
| includeMetered | boolean | no | also read X, which BILLS CREDITS per post read — ask the user first |
| max | number | no | cap how many posts to read in this run (default 40) |
| remeasure | boolean | no | ALSO re-read posts older than 7 days whose every reading came back empty or failed — use after post_performance reports posts "read but empty", or once a channel's reader has been fixed. Otherwise tho |
Import this brand's PAST posts from a channel into the performance record, so 'which hook works' can draw on history rather than only on what was published since Hermoso started recording. Supports facebook, instagram, threads, youtube, tiktok, pinterest and bluesky; the others say plainly why they cannot (LinkedIn has no enumerate-my-posts endpoint on our grant, X bills per read so it is excluded
| Parameter | Type | Required | Notes |
|---|---|---|---|
| channel | string | yes | which channel to import from |
| confirm | boolean | no | actually import — omit for a dry run that only quotes the cost |
| limit | number | no | how many posts this page (default 50, max 200) |
| cursor | string | no | resume from a previous run |
| accountRef | string | no | which Page / account, when the profile has more than one |
- Every tool — any call limit is its publisher’s, not ours
- No card, and mcprush charges nothing for it
- Runs at its publisher’s own address — this marketplace is not in the path
- Nothing to cap, because mcprush bills nothing
What counts against your monthly calls
| Tool | Unit | Calls used | Out of the allowance |
|---|
Nothing here is billable: mcprush charges nothing to install Hermoso: AI Ad Generator or to call it, at any volume.
Two independent axes, because powerful and malicious are different questions. The grade is threat only. The capability level is blast radius, and it is never a penalty on the grade — it is priced as one subtract-only term in the score, where you can see it.
| Term | Level | What it prices | Points |
|---|---|---|---|
| capability-exposure | critical | capability blast radius (critical) — client exposure if the model is manipulated | −10 |
| coverage-honesty | manifest | inspection depth (manifest) — how much of the target the scan could see | −4 |
What the scan could actually read
A grade is only as meaningful as its coverage, so the scanner publishes its own depth before it publishes its result.
The scan read each tool’s name, description, input schema and annotations exactly as the server listed them the last time it answered us; no code was reviewed. We ask the server again about once a month, and a list that changed is graded again.
The implementation source was not read — this reflects the declared tool metadata only.
Capability — what it could do if the model were manipulated
Tags derived from each tool’s schema and the implementation, not from what the tool calls itself. critical is the level these add up to.
| Tool | Capability tags | Why the tag was assigned |
|---|---|---|
| enable_tools | state-write | state-write: declares readOnlyHint: false |
| find_tools | no tags | |
| call_tool | external-sink | external-sink: declares readOnlyHint: false, destructiveHint: true, openWorldHint: true |
| hermoso_capabilities | no tags | |
| hermoso_credits | no tags | |
| buy_credits | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| report_bug | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| request_feature | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| billing_status | no tags | |
| upgrade_plan | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| set_auto_reload | state-write | state-write: declares readOnlyHint: false |
| list_brands | no tags | |
| use_brand | state-write | state-write: declares readOnlyHint: false |
| create_brand | state-write | state-write: declares readOnlyHint: false |
| delete_brand | external-sink | external-sink: declares readOnlyHint: false, destructiveHint: true, openWorldHint: true |
| upload_file | external-sink | external-sink: keyword "upload_file", declares readOnlyHint: false, openWorldHint: true |
| save_to_drive | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| import_from_cloud | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| list_drive_files | untrusted-input | untrusted-input: declares openWorldHint: true (interacts with entities outside the server) |
| get_drive_file | untrusted-input | untrusted-input: keyword "fetch", declares openWorldHint: true (interacts with entities outside the server) |
| update_drive_file | external-sink | external-sink: declares readOnlyHint: false, destructiveHint: true, openWorldHint: true |
| delete_drive_file | external-sink | external-sink: declares readOnlyHint: false, destructiveHint: true, openWorldHint: true |
| create_drive_folder | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| create_sheet | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| append_to_sheet | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| read_sheet | untrusted-input | untrusted-input: declares openWorldHint: true (interacts with entities outside the server) |
| export_swipefile_deck | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| list_notion_pages | untrusted-input | untrusted-input: declares openWorldHint: true (interacts with entities outside the server) |
| save_to_notion | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| append_to_notion_page | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| read_notion_page | untrusted-input | untrusted-input: declares openWorldHint: true (interacts with entities outside the server) |
| export_schedule_to_notion | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| query_notion_database | untrusted-input | untrusted-input: declares openWorldHint: true (interacts with entities outside the server) |
| update_notion_row | external-sink | external-sink: declares readOnlyHint: false, destructiveHint: true, openWorldHint: true |
| create_doc | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| append_to_doc | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| read_doc | untrusted-input | untrusted-input: declares openWorldHint: true (interacts with entities outside the server) |
| list_sheet_tabs | untrusted-input | untrusted-input: declares openWorldHint: true (interacts with entities outside the server) |
| update_sheet | external-sink | external-sink: declares readOnlyHint: false, destructiveHint: true, openWorldHint: true |
| clear_sheet_range | external-sink | external-sink: declares readOnlyHint: false, destructiveHint: true, openWorldHint: true |
| manage_sheet_tabs | external-sink | external-sink: declares readOnlyHint: false, destructiveHint: true, openWorldHint: true |
| format_sheet | external-sink | external-sink: declares readOnlyHint: false, destructiveHint: true, openWorldHint: true |
| update_doc | external-sink | external-sink: declares readOnlyHint: false, destructiveHint: true, openWorldHint: true |
| save_to_onedrive | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| list_onedrive_files | sensitive-sourceuntrusted-input | sensitive-source: keyword "list_files"; untrusted-input: declares openWorldHint: true (interacts with entities outside the server) |
| get_onedrive_file | untrusted-input | untrusted-input: keyword "fetch", declares openWorldHint: true (interacts with entities outside the server) |
| update_onedrive_file | external-sink | external-sink: declares readOnlyHint: false, destructiveHint: true, openWorldHint: true |
| delete_onedrive_file | external-sink | external-sink: declares readOnlyHint: false, destructiveHint: true, openWorldHint: true |
| share_onedrive_file | external-sink | external-sink: declares readOnlyHint: false, destructiveHint: true, openWorldHint: true |
| onedrive_file_permissions | external-sink | external-sink: declares readOnlyHint: false, destructiveHint: true, openWorldHint: true |
| convert_onedrive_file | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| create_onedrive_folder | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| plan_ad | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| clone_video | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| click_to_ad | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| generate_image | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| edit_image | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| headline_variants | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| resize_ad | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| localize_ad | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| make_thumbnail | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| generate_voice | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| generate_sfx | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| generate_music | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| generate_text | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| render_ad | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| make_template_ad | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| make_motion_film | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| finish_video | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| post_edit | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| edit_timeline | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| video_frames | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| fix_beat | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| clip_video | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| add_subtitles | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| make_explainer | code-execexternal-sink | code-exec: parameter "script"; external-sink: declares readOnlyHint: false, openWorldHint: true |
| resume_job | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| make_series | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| make_effect | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| product_sizzle | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| generate_video | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| generate_avatar | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| stitch_video | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| get_job | no tags | |
| plan_week | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| list_skills | no tags | |
| get_skill | no tags | |
| save_skill | state-write | state-write: declares readOnlyHint: false |
| delete_skill | state-write | state-write: declares readOnlyHint: false, destructiveHint: true |
| list_memory | no tags | |
| remember | state-write | state-write: declares readOnlyHint: false |
| forget | state-write | state-write: declares readOnlyHint: false, destructiveHint: true |
| tidy_memory | state-write | state-write: declares readOnlyHint: false, destructiveHint: true |
| list_swipefile | no tags | |
| save_to_swipefile | state-write | state-write: declares readOnlyHint: false |
| update_saved_creator | state-write | state-write: declares readOnlyHint: false, destructiveHint: true |
| list_playbooks | no tags | |
| save_playbook | state-write | state-write: declares readOnlyHint: false |
| delete_playbook | state-write | state-write: declares readOnlyHint: false, destructiveHint: true |
| list_creators | no tags | |
| save_creator | state-write | state-write: declares readOnlyHint: false |
| delete_creator | state-write | state-write: declares readOnlyHint: false, destructiveHint: true |
| update_brand | state-write | state-write: declares readOnlyHint: false, destructiveHint: true |
| store_get | no tags | |
| get_settings | no tags | |
| update_settings | state-write | state-write: declares readOnlyHint: false, destructiveHint: true |
| list_connectors | no tags | |
| list_connector_accounts | untrusted-input | untrusted-input: declares openWorldHint: true (interacts with entities outside the server) |
| set_connector_accounts | external-sink | external-sink: declares readOnlyHint: false, destructiveHint: true, openWorldHint: true |
| disconnect_connector | external-sink | external-sink: declares readOnlyHint: false, destructiveHint: true, openWorldHint: true |
| connect_connector | external-sink | external-sink: keyword "webhook", declares readOnlyHint: false, destructiveHint: true, openWorldHint: true |
| leave_connector | external-sink | external-sink: declares readOnlyHint: false, destructiveHint: true, openWorldHint: true |
| list_team | no tags | |
| invite_member | external-sink | external-sink: declares readOnlyHint: false, destructiveHint: true, openWorldHint: true |
| remove_member | external-sink | external-sink: declares readOnlyHint: false, destructiveHint: true, openWorldHint: true |
| set_role | external-sink | external-sink: declares readOnlyHint: false, destructiveHint: true, openWorldHint: true |
| list_jobs | no tags | |
| list_errors | no tags | |
| error_detail | no tags | |
| find_competitors | untrusted-inputexternal-sink | untrusted-input: keyword "web_search"; external-sink: declares readOnlyHint: false, openWorldHint: true |
| pull_competitor_ads | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| set_competitor_watch | external-sink | external-sink: declares readOnlyHint: false, destructiveHint: true, openWorldHint: true |
| list_watch_findings | no tags | |
| research_ads | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| search_meta_ads | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| search_google_ads | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| search_linkedin_ads | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| find_creators | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| search_posts | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| tiktok_ideas | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| search_tiktok | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| search_instagram | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| search_youtube | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| search_reddit | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| search_threads | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| search_meta_places | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| fetch_social_data | untrusted-inputexternal-sink | untrusted-input: keyword "fetch"; external-sink: declares readOnlyHint: false, openWorldHint: true |
| get_brand | no tags | |
| draft_brand | external-sink | external-sink: declares readOnlyHint: false, destructiveHint: true, openWorldHint: true |
| list_library | untrusted-input | untrusted-input: keyword "browse" |
| delete_library_item | external-sink | external-sink: declares readOnlyHint: false, destructiveHint: true, openWorldHint: true |
| fetch_asset | untrusted-input | untrusted-input: keyword "fetch" |
| analyze_video | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| repurpose_transcript | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| find_sound | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| make_insert | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| score_ad | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| check_video | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| reframe_video | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| repurpose_video | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| repurpose_image | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| add_presenter | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| insert_scene | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| upscale_video | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| finish_draft | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| restyle_video | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| edit_video | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| multiply_ad | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| hook_variants | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| dub_video | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| change_voice | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| recast_motion | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| recast_hook | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| face_check | untrusted-input | untrusted-input: declares openWorldHint: true (interacts with entities outside the server) |
| plan_variations | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| make_static_ads | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| make_listing_set | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| list_products | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| add_product | external-sink | external-sink: declares readOnlyHint: false, destructiveHint: true, openWorldHint: true |
| refresh_products | external-sink | external-sink: declares readOnlyHint: false, destructiveHint: true, openWorldHint: true |
| update_product | state-write | state-write: declares readOnlyHint: false, destructiveHint: true |
| remove_product | state-write | state-write: declares readOnlyHint: false, destructiveHint: true |
| competitor_teardown | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| check_ad_policy | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| clone_static | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| remix_static | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| mine_angles | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
| list_product_photos | state-write | state-write: declares readOnlyHint: false |
| set_product_image | external-sink | external-sink: declares readOnlyHint: false, destructiveHint: true, openWorldHint: true |
| fetch_app_screens | untrusted-inputexternal-sink | untrusted-input: keyword "fetch"; external-sink: declares readOnlyHint: false, openWorldHint: true |
| list_meta_posts | untrusted-input | untrusted-input: declares openWorldHint: true (interacts with entities outside the server) |
| list_published_posts | no tags | |
| list_hooks | no tags | |
| list_templates | no tags | |
| post_performance | no tags | |
| diagnose_posts | no tags | |
| collect_post_metrics | untrusted-inputexternal-sink | untrusted-input: keyword "fetch"; external-sink: declares readOnlyHint: false, openWorldHint: true |
| backfill_posts | external-sink | external-sink: declares readOnlyHint: false, openWorldHint: true |
Toxic-flow graph
The lethal trifecta, checked as a graph rather than as a checklist: untrusted input, a sensitive source and an external sink have to meet before there is a path worth worrying about.
Supply chain and provenance
This is the first scan of this surface here, so there is nothing yet to compare it against.
Every result on this tab comes from one deterministic pass over the published package — offline, rule by rule, and auditable line by line above. Same methodology version, same bytes, same score.
Release history
This is an address its publisher runs: which release it serves is theirs to choose, so there is nothing here to pin. The history below is for reading; the current release on file is 1.0.0.
No release note was published with this version.
One review per account, from a signed-in account that does not publish this listing. Nothing else is asked — you can say what you think before you install it. Publishers can reply once per review, and a review can be edited or deleted by whoever wrote it.
Any signed-in account that does not publish Hermoso: AI Ad Generator can review it, once — before installing it or after. A review from an account that has installed it is marked as one.
Nobody has reviewed this listing. The rating on the card is the mean of the reviews written here and nothing else, so there is no rating until somebody writes the first — which takes a signed-in account that does not publish it, and nothing else.