accueil / serveurs / Médias / io.github.hermoso-ai/hermoso
io.github.hermoso-ai/hermoso
serveur MCP (Model Context Protocol) · hermoso 0.1.435
Référencé dans l'annuaire. Seul le pentest comportemental fait foi, et il n'a pas encore été exécuté sur ce serveur.
Est-ce que io.github.hermoso-ai/hermoso est sûr ?
Au dernier audit MCP Hub, le serveur MCP « io.github.hermoso-ai/hermoso » est classé « Non encore testé ». Référencé dans l'annuaire. Seul le pentest comportemental fait foi, et il n'a pas encore été exécuté sur ce serveur. Le verdict est réévalué en continu ; consultez la fiche pour l'état à jour.
Méthode : MCP Hub analyse la description de chaque outil (couche 1) et, pour le verdict fort, exécute le serveur dans une micro-VM isolée avec leurres et réseau bloqué (couche 2). On publie des noms et des compteurs, jamais le contenu brut renvoyé par un outil.
Les 185 outils
- add_subtitles≈ 650 tokens
Burn subtitles into ANY video and get the .srt too; nothing is cut or re-rendered. Set textStyle only when the user describes a look (default: white sentence case, thin outline, bottom). Timed per sentence, or auto:'words' for phrases on their spoken words. Or pass cues to burn your own lines exactly, untranscribed (reel-style phrases, *stage directions*). burn:false returns only the .srt. Takes a video file or URL, a YouTube/Vimeo-style page, or a TikTok / Reel / X post link. No speech is refused and refunded. Runs in the background and lands in the Library.
Paramètres, retour & exemple (6)
Paramètresauto· string · optionnel · sentences | wordsburn· boolean · optionnel
false = only the .srtcues· array · optionnel
your own lines in seconds, no overlap, max 90 chars, no emojivideo· string · requis
the video to subtitletextStyle· ? · optionnel
the look: words, a preset or fields; omit for the defaultwordsPerCue· number · optionnel
Exemple d'appel{"name": "add_subtitles", "arguments": {"video": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - analyze_video≈ 225 tokens
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.
Paramètres, retour & exemple (4)
Paramètresurl· string · requis
the video URL (a served /generated/ path or a public http(s) video)words· ? · optionnel
true | 'only': each spoken word's start/endframes· boolean · optionnel
also return the frames as imagesanchors· array · optionnel
{afterWord|beforeWord|atWord, occurrence?, offset?} -> s
Exemple d'appel{"name": "analyze_video", "arguments": {"url": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - append_to_doc≈ 388 tokens
Append text to the end of a Google Doc Hermoso can reach — one it created (pass the documentId from create_doc) or one the user handed over with the Google file picker in the app (find its id with list_drive_files). Optional `dropdown:{title, options:[2-50], selected}` appends a Google Docs dropdown chip after the text — e.g. text "Status: " + dropdown {title:"Status", options:["Draft","In review","Approved"]} gives a brief a status the team can click to change; the chip is confirmed by reading the doc back. Set it later with update_doc dropdowns.
Paramètres, retour & exemple (3)
Paramètrestext· string · optionnel
text to append at the end of the doc (optional when a dropdown is passed)dropdown· object · optionnel
a dropdown chip to append after the textdocumentId· string · requis
the document id from create_doc
Exemple d'appel{"name": "append_to_doc", "arguments": {"documentId": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - append_to_sheet≈ 251 tokens
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.
Paramètres, retour & exemple (3)
Paramètresrows· array · requis
rows to append — array of row arraysrange· string · optionnel
range to append at (default A1 / first sheet)spreadsheetId· string · requis
the spreadsheet id from create_sheet
Exemple d'appel{"name": "append_to_sheet", "arguments": {"rows": [], "spreadsheetId": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - backfill_posts≈ 527 tokens
Import this brand's PAST posts from a channel into the performance record, so 'which hook works' can draw on history rather than only on what was published since Hermoso started recording. Supports facebook, instagram, threads, youtube, tiktok, pinterest and bluesky; the others say plainly why they cannot (LinkedIn and Reddit have no enumerate-my-posts endpoint on our grant, X bills per read so it is excluded from bulk import, Google Business has had no per-post insights since 2023, and Telegram's Bot API cannot read a chat's past messages at all — nothing published before Hermoso is recoverable through a bot token). BOUNDED, RESUMABLE AND QUOTED: it runs as a DRY RUN by default and tells you how many posts it found and what reading them will cost — pass confirm:true to import, and pass the returned cursor to continue. AN IMPORTED POST IS WEAKER EVIDENCE THAN A RECORDED ONE and is labelled 'backfilled': its hook is recovered ONLY where the post matches a Hermoso creation by asset or caption. A post made outside Hermoso stays UNATTRIBUTED — it counts toward channel and format totals but never votes on which hook works. Never guess a hook from a caption. Free.
Paramètres, retour & exemple (5)
Paramètreslimit· number · optionnel
how many posts this page (default 50, max 200)cursor· string · optionnel
resume from a previous runchannel· string · requis · facebook | instagram | threads | youtube | tiktok | pinterest | bluesky
which channel to import fromconfirm· boolean · optionnel
actually import — omit for a dry run that only quotes the costaccountRef· string · optionnel
which Page / account, when the profile has more than one
Exemple d'appel{"name": "backfill_posts", "arguments": {"channel": "facebook"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - billing_status≈ 258 tokens
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 AND THE BALANCE ONLY: the workspace owner's payment card and auto-reload belong to them and are not reported (billingScope:'member'). Never tell a member there is no card on file — the honest answer is that you cannot see it. plan.memberRate: media models at cost + 10%; plan.legacy: an older plan kept at its price.
Paramètres, retour & exemple (0)
Exemple d'appel{"name": "billing_status", "arguments": {}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - buy_credits≈ 545 tokens
Out of credits? Top up with a credit PACK. Call with no argument to list the available packs (id · credits · price). If the account has a saved card and you have billing-admin rights, calling with `pack` quotes the exact charge and calling again with confirm:true AND the quote's quote_token charges the saved card instantly (same one-click top-up as the app — no redirect). If there's no saved card yet, you get a Stripe checkout URL to hand your human for the FIRST purchase; their card saves for one-click after that. Packs only; subscriptions are managed by a person in Settings -> Billing. IF YOU ARE AN AGENT HOLDING YOUR OWN PAYMENT CREDENTIAL, there is a third path that needs no human at all: `POST /api/billing/machine-payment` with a `packId` answers HTTP 402 carrying an MPP challenge, and grants the pack once you authorise and retry with the credential — the same packs, the same prices, the same credits. `GET /api/billing/config` carries a `machinePayments` block listing the packs with their per-credit rates and saying whether that lane is enabled on this server. Most agents do NOT have their own credential yet, so the checkout link above remains the normal path. To stop running out entirely, turn on auto-reload with set_auto_reload (admin) — low balances then top themselves up from the saved card automatically.
Paramètres, retour & exemple (3)
Paramètrespack· string · optionnel
the pack id to buy (e.g. pack-2k) — omit to list the available packs firstconfirm· boolean · optionnel
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 · optionnel
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
Exemple d'appel{"name": "buy_credits", "arguments": {"pack": "texte", "confirm": true, "quote_token": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - call_tool≈ 357 tokens
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_tools switches a group on server-side, but such a host keeps the list it fetched at connect time. Arguments are validated against the tool's own schema and a mistake is answered with the expected parameters, not a silent default. Refused by name, with the way out, when the tool needs a connector this workspace has not made or is withheld by the host's own policy.
Paramètres, retour & exemple (2)
Paramètresargs· object · optionnel
the tool's arguments as an object, exactly as its own schema takes themname· string · requis
the tool name exactly as find_tools returned it, e.g. create_meta_lead_form
Exemple d'appel{"name": "call_tool", "arguments": {"name": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - cancel_scheduled≈ 226 tokens
Remove a queued post before it goes out. Get the id from list_scheduled. Only works while it is still queued — something already published cannot be unsent (use manage_meta_post to delete a Facebook/Instagram post after the fact).
Paramètres, retour & exemple (2)
Paramètresid· string · requis
the scheduled post id from list_scheduledbrand· string · optionnel
WHICH PROFILE this post is in — id or exact name from list_brands; needed when it is not the profile this connection is pinned to. A name that matches no profile, or two, is REFUSED.
Exemple d'appel{"name": "cancel_scheduled", "arguments": {"id": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - change_voice≈ 244 tokens
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.
Paramètres, retour & exemple (2)
Paramètresvideo· string · requis
the source video URLvoice· string · optionnel
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 — name it the way it is saved on their cast
Exemple d'appel{"name": "change_voice", "arguments": {"video": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - check_ad_policy≈ 328 tokens
Pre-flight ad copy against Meta's REAL, live Advertising Standards before you run it — a flat 1-credit check. Pulls Meta's actual policy pages and returns a verdict (pass / fix / block) where every flagged issue QUOTES Meta's own policy text verbatim plus a compliant rewrite that keeps the sell. It's a check, not an edit — it never changes the creative. Especially worth running for regulated-adjacent categories (health/supplements, weight-loss or beauty results claims, finance/crypto/insurance, alcohol, dating, gambling) or ANY strong/absolute/guaranteed claim.
Paramètres, retour & exemple (4)
Paramètrescopy· string · requis
the ad copy / script / on-screen text to checkclaims· string · optionnel
the claims / proof points the ad makescategory· string · optionnel
the product category — helps pick the relevant policy pagesimageDescription· string · optionnel
a description of the creative / image when relevant
Exemple d'appel{"name": "check_ad_policy", "arguments": {"copy": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - clear_sheet_range≈ 335 tokens
Empty a range of cells in a Google Sheet, leaving the rows themselves in place. DESTRUCTIVE: call it WITHOUT confirm first and nothing is cleared — you get back the real number of filled cells in that exact range. Show the user that number, get an unambiguous yes, then call again with confirm:true AND confirmCells set to it. The echo is not ceremony: it is what catches naming A1:Z1000 when you meant A1:Z10, which is the mistake that actually happens. There is deliberately NO default range. The clear is read back and reported as confirmed only if the range really is empty afterwards. To remove a whole tab instead, use manage_sheet_tabs.
Paramètres, retour & exemple (5)
Paramètresrange· string · requis
the range to clear, e.g. "A2:D50" or "Sheet1!A2:D50"confirm· boolean · optionnelsheetUrl· string · optionnelconfirmCells· number · optionnel
echo back the filled-cell count the unconfirmed call reportedspreadsheetId· string · optionnel
Exemple d'appel{"name": "clear_sheet_range", "arguments": {"range": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - clip_video≈ 768 tokens
Cut ONE long video into several RANKED, ready-to-post short clips (podcast, webinar, interview, talk, long ad cut -> Reels/Shorts/TikTok). Transcribes with timestamps, picks the strongest SELF-CONTAINED moments, then cuts + reframes each with ffmpeg — no video model renders anything, so it is fast and cheap. THE VERTICAL REFRAME IS SUBJECT-AWARE: one cheap vision call per clip (billed as its own event) picks a SINGLE crop offset held for the whole clip, so a speaker sitting camera-left is not cropped out and the framing never drifts inside a clip; with nothing to discard or no single subject it stays dead centre — read `reframedToSubject` and each clip's `reframeWhy` back rather than assuming either way. ACCEPTS: a YouTube link (or Vimeo / Loom / Dailymotion / Streamable / Rumble / Wistia / Twitch / TED), a direct https .mp4/.mov/.webm, or a Hermoso /generated/ URL (upload_file turns a local file into one). NOT supported: TikTok / Instagram / Facebook links, and anything age-restricted, private, members-only, geo-blocked or still LIVE — those fail fast with the real reason and are fully refunded, so ask for a direct file or an upload rather than retrying. Source ~15s to ~600MB; only the first ~40 minutes is analysed (truncated:true says so). Cost: a ~7-credit hold settled to the exact transcription + encode cost, plus the clip-selection model's tokens as their own small event. RETURNS clips[] — each its OWN served mp4 URL, title, hook, ready-to-post caption, 0-100 score and source timecode. SUBTITLES ARE BURNED IN BY DEFAULT (slim white CAPS, thin black outline, bottom safe band, no box) because short-form is watched on mute; captions:false for clean footage. TIMING IS APPROXIMATE, NOT WORD-LEVEL — cues follow the transcript's per-sentence timestamps, split by character count; never promise frame-accurate sync. captionsBurned counts the clips that really carry a burned track and captionNote says why any are bare.
Paramètres, retour & exemple (4)
Paramètrescount· number · optionnel
how many clips to cut, 1-8 (default 4)video· string · requis
the long video to clip — a YouTube/Vimeo/Loom/Dailymotion/Streamable/Rumble/Wistia/Twitch/TED watch URL, a direct https .mp4/.mov/.webm, or a Hermoso /generated/ URLcaptions· boolean · optionnel
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 readable speech is delivered bare rather than captioned with a guess, and the result says which.aspectRatio· string · optionnel
clip shape: '9:16' (default), '1:1', '16:9', any 'W:H', or 'keep' for the source framing
Exemple d'appel{"name": "clip_video", "arguments": {"video": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - clone_static≈ 334 tokens
One-click STATIC-AD CLONE (the web app calls it Clone): rebuild a competitor/reference STATIC (image) ad as an on-brand version — SAME layout, composition and energy, but YOUR product, brand colours, logo and voice, with every trace of the source brand removed. Pass `imageUrl` = the static ad image to clone. Uses your saved brand (pass brandId to target a specific profile — that switches this key's active profile like use_brand). IMAGES ONLY — for a video ad use clone_video with its link, then render_ad. Bills as one image generation.
Paramètres, retour & exemple (2)
ParamètresbrandId· string · optionnel
a profile id/name from list_brands to clone for; omit to use the active profileimageUrl· string · requis
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 claim against Hermoso (hermoso.ai/terms). Paid plan only; recorded.
Exemple d'appel{"name": "clone_static", "arguments": {"imageUrl": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - clone_video≈ 554 tokens
Remake a video you like FOR THIS BRAND from its link — a TikTok, Instagram Reel, Facebook video or reel, X post, YouTube Short or video, or a direct video file URL. Hermoso WATCHES it first (frames across the whole clip plus a transcript of the voiceover, on-screen text and cut map), then plans a storyboard that keeps its hook device, structure, jump cuts and pacing while swapping in THIS brand's product, cast, setting and words — never the original's words, face or brand. The new ad MATCHES THE ORIGINAL'S LENGTH (capped at 60s) unless durationSeconds is given. Renders nothing: pass the returned creative to render_ad to make the video. Costs the plan plus about 2 credits to read the link. The reply says exactly what was watched, and when a platform will not hand over the footage (YouTube sometimes refuses servers) it says the plan rests on the captions and thumbnail only. For a local file, upload_file it first and pass the URL.
Paramètres, retour & exemple (6)
Paramètresurl· string · requis
the video to clone — a TikTok / Instagram Reel / Facebook / X / YouTube link, or a direct https video file URLbrand· ? · optionnel
brand name or profile object; OMIT to use the workspace's saved brand (see get_brand)changes· string · optionnel
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")product· string · optionnel
what the new ad sells, plus any angle or offer; omit to use the saved brand's productlanguage· string · optionnel
language for the new ad's script and copy — default EnglishdurationSeconds· number · optionnel
override the length in seconds; omit to match the original
Exemple d'appel{"name": "clone_video", "arguments": {"url": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - collect_post_metrics≈ 474 tokens
Fetch fresh performance numbers for this brand's recorded posts and store them as a time-series. Metrics ACCRUE, so a post is read at ~24 hours and again at ~7 days; this collects whichever readings are due and skips the ones already taken. A channel that cannot report a metric records it as ABSENT with the reason — never as zero — and a read that fails is recorded as 'could not tell', which contributes to nothing. X IS SKIPPED BY DEFAULT because X bills us per API call: pass includeMetered:true to include it, and tell the user it costs credits BEFORE you do. The skip is always reported so a channel missing from the numbers is never mistaken for one that performed badly. Free except for X.
Paramètres, retour & exemple (4)
Paramètresmax· number · optionnel
cap how many posts to read in this run (default 40)brand· string · optionnel
WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CALL ONLY. A name that matches no profile, or two, is REFUSED and nothing is done.remeasure· boolean · optionnel
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 those windows stay closed.includeMetered· boolean · optionnel
also read X, which BILLS CREDITS per post read — ask the user first
Exemple d'appel{"name": "collect_post_metrics", "arguments": {"max": 1, "brand": "texte", "remeasure": true, "includeMetered": true}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - competitor_teardown≈ 400 tokens
Tear a competitor's ad strategy down into an actionable playbook: their opening-hook MIX, longest-running campaign THEMES, the WHITE SPACE nobody in their set runs, 2-3 render-ready COUNTER-PLAYS, and the territories they own that you should avoid. Pass `competitor` {name, domain?}. CONTRACT: supply `ads` (raw ad objects from a prior pull_competitor_ads / search_meta_ads call) to tear exactly those down, OR omit `ads` and this pulls the competitor's real Meta ads first (spends a credit or two, longest-running = proven winners). Auto-tailors the white space + counter-plays to YOUR saved brand. Spends credits (free when you pass ads).
Paramètres, retour & exemple (3)
Paramètresads· array · optionnel
ad objects to tear down (from pull_competitor_ads / search_meta_ads). Omit to auto-pull their Meta ads first.language· string · optionnel
output language (default English)competitor· object · requis
the competitor to tear down
Exemple d'appel{"name": "competitor_teardown", "arguments": {"competitor": {}}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - connect_connector≈ 630 tokens
Connect an account that links with a KEY rather than a sign-in screen, from here, with no browser: AppLovin Ads, Stripe, Intercom, Vercel, ChatGPT Ads, Apple Ads, Bluesky, Telegram, Bing Webmaster Tools, PostHog, Mixpanel, Amplitude, Slack, Discord, Webhook. Pass provider and fields in that provider's own field names (listed below, * = required). The key is checked live with the provider before anything is saved, exactly as the app's Connectors page checks it, and the reply is read back from the saved connection. OFFER BOTH WAYS AND LET THE USER CHOOSE: a key pasted into this chat stays in this conversation's history, while pasting it in the app (Workspace > Connectors, or the one-click link https://app.hermoso.ai/?connect=<provider>) keeps it out of the chat. Hermoso never repeats a submitted key back. An account that connects through the provider's own sign-in screen (OAuth) cannot be connected here: this answers with the link to hand the user instead. Apple Ads with no key material first generates a signing key pair and returns the public key to register with Apple plus a setupToken to send back. Fields: applovin_ads {reportKey, eventKey, apiKey, capiKey, accountId} · stripe {apiKey*} · intercom {accessToken*, region} · vercel {token*, team} · openai_ads {apiKey*} · apple_ads {clientId, teamId, keyId, privateKey, setupToken, orgId} · bluesky {identifier*, appPassword*, pds} · telegram {token*} · bing_webmaster {apiKey*} · posthog {apiKey*, region, host, projectId} · mixpanel {username*, secret*, projectId*, region, workspaceId} · amplitude {apiKey*, secretKey*, region, host} · slack {webhookUrl*} · discord {webhookUrl*} · webhook {webhookUrl*}.
Paramètres, retour & exemple (2)
Paramètresfields· object · optionnel
that provider's own field names and values, e.g. {"apiKey":"…"}; the names for each provider are in the descriptionprovider· string · requis
the connector id: applovin_ads, stripe, intercom, vercel, openai_ads, apple_ads, bluesky, telegram, bing_webmaster, posthog, mixpanel, amplitude, slack, discord, webhook
Exemple d'appel{"name": "connect_connector", "arguments": {"provider": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - convert_onedrive_file≈ 431 tokens
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 (CR2, NEF, ARW, DNG) that nothing else in this product can open. Use it to turn a client’s deck into images you can actually put in an ad, to get a usable JPG out of a designer’s PSD or a photographer’s raw file, or to hand someone a PDF of a spreadsheet. CONVERTING TO JPG REQUIRES BOTH width AND height — Microsoft refuses the call without them. The result is stored at a durable Hermoso URL you can pass straight to a render or a post; Microsoft’s own conversion link expires within minutes, so do not hand that one to anyone. Needs OneDrive connected — no new permission.
Paramètres, retour & exemple (4)
Paramètreswidth· number · optionnel
REQUIRED for jpg — output width in pixelsfileId· string · requis
the OneDrive item id, from list_onedrive_filesformat· string · optionnel · pdf | jpg
default pdfheight· number · optionnel
REQUIRED for jpg — output height in pixels
Exemple d'appel{"name": "convert_onedrive_file", "arguments": {"fileId": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - create_brand≈ 300 tokens
Add a NEW profile (a brand, client, creator or personal workspace) and switch to it. Each has its OWN brand details, memory, swipefile, Library, avatars, skills, playbooks and connectors; nothing leaks between them. Use draft_brand to FILL it and update_brand to edit it. Re-running with the same name returns the existing profile instead of a duplicate. Creating it is free. Drafting a NEW brand into it (draft_brand) needs the credits for its research on the balance, about 100; with fewer, draft_brand refuses before anything runs or is charged and says how many it needs and how many the account has.
Paramètres, retour & exemple (2)
Paramètresname· string · requis
the name for the new profile (a brand, client or creator name)activate· boolean · optionnel
switch this connection to the new profile (default true); everything you do next scopes to it
Exemple d'appel{"name": "create_brand", "arguments": {"name": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - create_doc≈ 182 tokens
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).
Paramètres, retour & exemple (2)
Paramètrestext· string · optionnel
body text to inserttitle· string · optionnel
document title
Exemple d'appel{"name": "create_doc", "arguments": {"text": "texte", "title": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - create_drive_folder≈ 234 tokens
Create a folder in the user’s Google Drive (optionally nested under parentId) to organize saved files. Returns the folder id + webViewLink. Use that ID as update_drive_file’s moveToFolderId or as parentId for a nested folder. NOTE: save_to_drive’s `folder` is a NAME, not this id — it find-or-creates a folder by that name, so pass the folder NAME there (or omit and just save, then move with update_drive_file).
Paramètres, retour & exemple (2)
Paramètresname· string · requis
folder nameparentId· string · optionnel
parent folder id for a nested folder (default: Drive root)
Exemple d'appel{"name": "create_drive_folder", "arguments": {"name": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - create_onedrive_folder≈ 210 tokens
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.
Paramètres, retour & exemple (2)
Paramètresname· string · requis
folder nameparentId· string · optionnel
parent folder id for a nested folder (default: OneDrive root)
Exemple d'appel{"name": "create_onedrive_folder", "arguments": {"name": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - create_sheet≈ 240 tokens
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).
Paramètres, retour & exemple (2)
Paramètresrows· array · optionnel
rows to write — array of row arrays; first row = headerstitle· string · optionnel
spreadsheet title
Exemple d'appel{"name": "create_sheet", "arguments": {"rows": [], "title": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - delete_brand≈ 471 tokens
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 unambiguous yes, then call again with confirm:true — plus, if the workspace is not empty, confirmName set to its exact name and confirmConnectors set to the number of connected accounts it reported. Those two exist because confirming INTENT does not prove you picked the right WORKSPACE, and a wrong target is how a live profile was destroyed. The account's FIRST/anchor profile cannot be deleted this way (it holds the workspace's root storage) — that one is replaced from the app.
Paramètres, retour & exemple (4)
Paramètresbrand· string · requis
profile id or exact name from list_brandsconfirm· boolean · optionnel
REQUIRED true — this destroys the whole profile and cannot be undoneconfirmName· string · optionnel
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 itconfirmConnectors· number · optionnel
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 can do it
Exemple d'appel{"name": "delete_brand", "arguments": {"brand": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - delete_creator≈ 212 tokens
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.
Paramètres, retour & exemple (2)
Paramètresid· string · requis
the creator id (from list_creators)brand· string · optionnel
profile id/name from list_brands, this call only
Exemple d'appel{"name": "delete_creator", "arguments": {"id": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - delete_drive_file≈ 201 tokens
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.
Paramètres, retour & exemple (3)
ParamètresfileId· string · requis
the Drive file idconfirm· boolean · optionnel
REQUIRED truepermanent· boolean · optionnel
true = delete forever; default trashes (recoverable)
Exemple d'appel{"name": "delete_drive_file", "arguments": {"fileId": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - delete_linkedin_lead_subscription≈ 222 tokens
Remove a lead notification webhook (subscriptionId from list_linkedin_lead_subscriptions). Leads themselves are unaffected and stay readable; only the real-time delivery stops. Read back from LinkedIn. Free.
Paramètres, retour & exemple (3)
ParamètrespageId· string · optionnel
the company Page that owns the form — from list_linkedin_pages; omit when one Page is sharedadAccountId· string · optionnel
read forms owned by an AD ACCOUNT instead of a PagesubscriptionId· string · requis
Exemple d'appel{"name": "delete_linkedin_lead_subscription", "arguments": {"subscriptionId": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - delete_onedrive_file≈ 169 tokens
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.
Paramètres, retour & exemple (2)
ParamètresfileId· string · requis
the OneDrive item idconfirm· boolean · optionnel
REQUIRED true
Exemple d'appel{"name": "delete_onedrive_file", "arguments": {"fileId": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - delete_playbook≈ 173 tokens
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.
Paramètres, retour & exemple (2)
Paramètresid· string · requis
the playbook id (from list_playbooks)brand· string · optionnel
profile id/name from list_brands, this call only
Exemple d'appel{"name": "delete_playbook", "arguments": {"id": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - delete_skill≈ 171 tokens
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.
Paramètres, retour & exemple (2)
Paramètresid· string · requis
the custom skill id (from list_skills)brand· string · optionnel
profile id/name from list_brands, this call only
Exemple d'appel{"name": "delete_skill", "arguments": {"id": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - diagnose_posts≈ 640 tokens
WHAT TO FIX NEXT, post by post — and the tool that fixes it. post_performance tells you which hook is AHEAD; this tells you what is WRONG with a given post and where the next edit goes. Under-distributed is A HOOK PROBLEM (change the opening: mine_angles, then list_hooks, then plan_variations). Seen but not held is A RETENTION PROBLEM (plan_variations to rebuild the middle against the same hook). Seen, held, and still not converting is AN OFFER PROBLEM. FOUR REFUSALS, AND YOU SHOULD REPEAT THEM RATHER THAN PAPER OVER THEM: (1) a post younger than ~24h is TOO EARLY and is never called a failure — it has not had its run; (2) a metric the platform does not publish is UNMEASURED, never zero — Facebook has published no post reach since 2026-06-15, Reddit publishes no impressions, and Google Business publishes nothing per-post at all; (3) below 5 measured posts on a channel there is no baseline of the brand's own, and the ONLY fallback is a published short-video hook floor that is NOT our measured number and does not transfer off TikTok/Instagram/YouTube — it is attributed in the output and you should attribute it too; (4) it does not always find a problem, and 'nothing here needs fixing' is a real answer rather than a failure to look. Hermoso cannot see conversions for an organic post — no channel reports installs or purchases against a post id — so the offer rung runs ONLY when the user tells you they are not converting and you pass converting:false. Print `summary` verbatim. Read-only, 0 credits.
Paramètres, retour & exemple (3)
Paramètreslimit· number · optionnel
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 normal.channel· string · optionnel
restrict to one channel: facebook, instagram, threads, x, linkedin, youtube, tiktok, reddit, pinterestconverting· boolean · optionnel
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 do not know; we cannot measure it.
Exemple d'appel{"name": "diagnose_posts", "arguments": {"limit": 1, "channel": "texte", "converting": true}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - disconnect_connector≈ 475 tokens
Disconnect a third-party account from this workspace (Meta, Google Ads, Google Drive/Sheets/Docs, YouTube, TikTok, LinkedIn, X, Reddit, Pinterest, Google Business, Microsoft Advertising/OneDrive, Slack, …). This always drops the stored credentials, so every tool for that provider stops working immediately and posts/campaigns already published are NOT affected. IT NEVER REVOKES THE GRANT AT THE PROVIDER: the authorisation stays in place, so the same account keeps working in any other brand it is connected to, until the user removes Hermoso in that provider's own settings. The unconfirmed call names where to do that (list_connectors carries revokesAtProvider:"local") — relay it rather than promising a revoke. RECONNECTING NEEDS A BROWSER (the provider's consent screen) — an agent cannot undo this. Name the provider to the user, then call with confirm:true. Use list_connectors for the exact provider ids.
Paramètres, retour & exemple (3)
Paramètresaccount· string · optionnel
on a channel with several connected accounts (TikTok, X, YouTube, Threads, Bluesky, Telegram, Reddit, Pinterest): remove ONLY this account (@handle or id from list_connector_accounts) and keep the othersconfirm· boolean · optionnel
REQUIRED true — reconnecting a sign-in account afterwards needs the user's browser; a paste-a-key account is reconnected with connect_connectorprovider· string · requis
provider id exactly as list_connectors reports it, e.g. "meta", "google_ads", "youtube", "linkedin"
Exemple d'appel{"name": "disconnect_connector", "arguments": {"provider": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - draft_brand≈ 412 tokens
Fill in a profile (a business brand or a creator) from a website domain, a free-text description, or a social handle, into a {name, products, logo, …} object you can pass to plan_ad / generate. 0 credits. IMPORTANT: a domain can resolve to a DIFFERENT company than intended (e.g. bala.com is an engineering firm, not the Bala fitness brand at shopbala.com). Before spending any credits on research or renders, VERIFY the returned `name` (and `summary`) match the brand the user meant; if it looks wrong, re-draft with the correct domain or a description (pass save:false until confirmed) — this tool cannot ask the user, so the caller owns that check.
Paramètres, retour & exemple (5)
Paramètressave· boolean · optionnel
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 overwrite the saved ones), false to never savedomain· string · optionnel
a website to scrapeplatform· string · optionnel
platform for socialHandle (instagram/tiktok/…)description· string · optionnel
a free-text brand description (no website)socialHandle· string · optionnel
a social handle to draft from (influencers/creators) — pair with platform
Exemple d'appel{"name": "draft_brand", "arguments": {"save": true, "domain": "texte", "platform": "texte", "description": "texte", "socialHandle": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - dub_video≈ 329 tokens
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.
Paramètres, retour & exemple (5)
Paramètresvideo· string · requis
the source video URLvoice· string · optionnel
optional, e.g. 'Aria' (woman) or 'George' (man): a native voice of that sex speaks. Default: the source speaker's sex.dryRun· boolean · optionnel
quote onlyscript· string · optionnel
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 wrong.language· string · requis
target language, e.g. 'Spanish', 'de', 'French (Canada)'
Exemple d'appel{"name": "dub_video", "arguments": {"video": "texte", "language": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - duplicate_scheduled≈ 886 tokens
Copy an existing scheduled or already-published post into a NEW queued post — the way to run a creative again, reuse a post that worked as the starting point for the next one, or re-send something after it went out. It copies the caption, media, per-channel captions, title, description, tags and the target board / Page / company Page / listing, and ANY of those can be overridden in the same call. Give a new time in `at`, or useQueue:true to drop it into the brand’s next free posting slot. The copy is INDEPENDENT — editing or cancelling it never touches the original — and it is a genuinely new post rather than a re-send, so it publishes even where the original already did. To re-fire only the channels that FAILED, use retry_scheduled instead.
Paramètres, retour & exemple (21)
Paramètresat· string · optionnel
when the copy goes out — ISO timestamp or epoch milliseconds (default: an hour from now)id· string · requis
the post to copy, from list_scheduledlink· string · optionnelbrand· string · optionnel
WHICH PROFILE this post is in — id or exact name from list_brands; needed when it is not the profile this connection is pinned to. A name that matches no profile, or two, is REFUSED.title· string · optionnelchatId· string · optionnel
TELEGRAM — which chat, group or channel the copy goes to (@username or numeric id)ideaId· string · optionnel
short id of the content-plan idea this post came frompageId· string · optionnel
FACEBOOK / INSTAGRAM / THREADS — which connected Page (list_meta_pages)recipe· string · optionnel
the post's FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per formatboardId· string · optionnel
PINTEREST — the board for the copy (list_pinterest_boards)message· string · optionnel
a different caption for the copycaptions· object · optionnel
per-channel caption overrides for the copychannels· array · optionnel
post the copy to these channels instead of the original’simageUrl· string · optionneltimezone· string · optionnel
IANA zone for the queue, e.g. "America/New_York"useQueue· boolean · optionnel
instead of naming a time, take the brand’s next free posting slotvideoUrl· string · optionnelimageUrls· array · optionnel
CAROUSEL — an ORDERED list of image URLs published as ONE swipeable postlocationId· string · optionnel
GOOGLE BUSINESS — which listing (list_business_locations)visibility· string · optionnel · public | unlisted | private | draftlinkedinOrganizationId· string · optionnel
LINKEDIN — publish the copy as this company Page (list_linkedin_pages)
Exemple d'appel{"name": "duplicate_scheduled", "arguments": {"id": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - edit_image≈ 644 tokens
EDIT an existing image in place with a plain-language instruction and keep everything else: 'make the headline bigger', 'add our logo bottom right', 'swap the background for a kitchen', 'remove the person on the left', 'erase all the text'. Pass `image` (URL, Library item, upload_file URL or local path) and `instruction`. The same edit the web Studio's Edit runs: composition, aspect ratio, people and every untouched line of text stay as they are; the saved brand's real name and website are pinned so an added line never invents one, and the brand's real logo is attached when the instruction asks for the logo. Set removal:true when the edit STRIPS text, branding or an object, so nothing branded is put back. When the saved brand has a product photo and the ad shows that product, the photo rides with the edit and the product's label is read and checked against it: re-printed from the photo only when it came out wrong (the check alone is charged when it is right; one small extra charge finds the product first). fixLabel:false turns that off. For a precise region, pass `mask` (see generate_image). One image edit's credits; returns the new image URL. For a new image from a prompt use generate_image; to rebuild a competitor's ad for your brand use clone_static.
Paramètres, retour & exemple (6)
Paramètresmask· string · optionnel
optional mask image (URL or local path) marking the region to change: transparent = change, or white = change on an opaque maskimage· string · requis
the image to edit: URL, Library item URL, upload_file URL or local pathdryRun· boolean · optionnel
true = return the estimated credits this edit reserves and render nothingremoval· boolean · optionnel
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-addedfixLabel· boolean · optionnel
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)instruction· string · requis
the change to make, in plain words (pass the user’s own words for a removal or plain photo edit)
Exemple d'appel{"name": "edit_image", "arguments": {"image": "texte", "instruction": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - edit_video≈ 932 tokens
EDIT an existing clip from a plain instruction (video-to-video): the motion, timing, framing and cut stay, the named thing changes. 'change only the mug to red', 'make it nighttime', 'restyle it as claymation'. Your words are wrapped so the model keeps everything else identical, changes only what you named and repeats that lock; lighting is kept unless the change needs new light (set lighting to force either); literal:true sends your words as written. One change per call holds best. previewFirstFrame:true edits ONE still first (one image edit; previewAt picks the second) and quotes the clip; nothing else runs until you call again, ideally with previewStill. The reply scores how well the shot held outside the change (free) and flags an edit that touched more than asked. NOT for cuts/trims/end cards (post_edit), a new video (generate_video / render_ad), translation (dub_video) or a saved creator's face (recast_motion). Best on 3-15s clips.
Paramètres, retour & exemple (13)
Paramètresvideo· string · requis
the source video URL (a render, job result or list_library)engine· string · optionnel · auto | seedance | kling
default auto: Seedance 2.5 Edit without a real-looking person, else Kling O3 Editliteral· boolean · optionnel
true = send the instruction exactly as written, with no preserve/lock wrapperelements· array · optionnel
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 instructionlighting· string · optionnel · auto | preserve | relight
default auto: keep the source light unless the change needs new light (night, a lamp, fire). 'preserve' or 'relight' forces itfaceRoute· string · optionnel · face_lane
'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.keepAudio· boolean · optionnel
default true: keep the source audio; false = silentpreviewAt· number · optionnel
second to preview (default 0); resend with previewStillreference· string · optionnel
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 lightinstruction· string · requis
the change, in the user’s own wordspreviewStill· string · optionnel
the still a previewFirstFrame call returned; the clip then matches the changed thing to itinteractionId· string · optionnel
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.previewFirstFrame· boolean · optionnel
true = edit ONE still of the first frame first and quote the clip; the paid clip does not run
Exemple d'appel{"name": "edit_video", "arguments": {"video": "texte", "instruction": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - enable_tools≈ 565 tokens
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 — claude.ai and ChatGPT do — will not show the new tools until it reconnects, and this tool says so in its reply rather than reporting a success you cannot use. The connect-time route that always works is `?tools=all` on the server URL. The default roster is CORE-FIRST: the core tools plus a few that make the connection drivable. Every other tool is held out of the LIST on SIZE alone — the whole registry is several hundred thousand tokens of schema re-sent on every turn, and a roster far past the 30-50 tool mark measurably degrades tool choice. The heaviest groups are `ads`, `analytics`, `channel_admin`: paid-campaign management across every ad platform is most of the total schema weight. NOTHING held out is unfinished or unsafe, and nothing is unreachable — find_tools finds it and call_tool runs it. CALL THIS WHEN A WHOLE AREA IS IN PLAY. If the user settles into building, budgeting, targeting or reporting on ad campaigns, call enable_tools({groups:['ads']}) and the tools appear. If they ask about their own site or product analytics, a tag/tracking container, or how a search engine crawls, indexes or ranks their site, call enable_tools({groups:['analytics']}). Groups: core, research, create, channels, channel_admin, analytics, ads, files, workspace — or 'all'. Free, instant, and it never turns anything off.
Paramètres, retour & exemple (1)
Paramètresgroups· array · requis
Groups to switch on, e.g. ['ads']. Unknown names are refused by name rather than ignored.
Exemple d'appel{"name": "enable_tools", "arguments": {"groups": []}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - error_detail≈ 169 tokens
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.
Paramètres, retour & exemple (1)
Paramètresfingerprint· string · requis
the `fp` value from list_errors
Exemple d'appel{"name": "error_detail", "arguments": {"fingerprint": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - export_swipefile_deck≈ 358 tokens
Turn a SWIPEFILE COLLECTION into a real Google Slides deck — one slide per saved ad, carrying the creative, the brand, the ad copy, the run dates with the run length, and the platform. This is the thing a marketer actually presents to a client or a team. Returns the presentation id + URL. Creates a NEW deck every time: under the drive.file scope Hermoso can only touch files it created, so it cannot add slides to a deck the user already has. A creative whose ad-library link has expired cannot be embedded — Meta signs those URLs with a short expiry — so that slide says so in words and keeps its copy and run dates, and the reply reports how many. Needs Google Drive connected (Settings > Connectors > Google Drive — one connection covers Drive, Sheets, Docs and Slides).
Paramètres, retour & exemple (3)
Paramètreslimit· number · optionnel
max ads to include, 1-60 (default 30)title· string · optionnel
deck title (default: the collection name)collection· string · optionnel
the swipefile collection to export, by name or id (default: the first collection)
Exemple d'appel{"name": "export_swipefile_deck", "arguments": {"limit": 1, "title": "texte", "collection": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - fetch_app_screens≈ 303 tokens
Pull an APP brand's REAL App Store screenshots into the workspace brand, so a screen-hungry native format can use them. Use when the brand has 0–1 app screens on file and you want make_template_ad(template:'app-ui-tour'), or the user asks to 'pull my app's screenshots'. Pass appName (defaults to the saved brand's name). FREE — a keyless App Store lookup. It needs a CONFIDENT match: an ambiguous or unknown app returns 0 screens and saves nothing, which you should relay plainly rather than retrying with guesses. On success the screens are saved to the brand (durable URLs) and are immediately usable.
Paramètres, retour & exemple (2)
ParamètresappName· string · optionnel
the app's name to look up on the App Store — defaults to the saved brand's namebrandId· string · optionnel
a profile id/name from list_brands to save the screens onto; omit to use the active profile
Exemple d'appel{"name": "fetch_app_screens", "arguments": {"appName": "texte", "brandId": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - fetch_asset≈ 152 tokens
Resolve a generated asset reference (a /generated/… path or any URL) to a clickable absolute URL + a direct download URL.
Paramètres, retour & exemple (2)
Paramètresurl· string · requis
the asset url or /generated/ pathname· string · optionnel
optional filename for the download
Exemple d'appel{"name": "fetch_asset", "arguments": {"url": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - fetch_social_data≈ 335 tokens
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, Bluesky, Truth Social, Rumble, Spotify, SoundCloud, GitHub, Google search, link-in-bio pages (Linktree etc.). Param names vary per endpoint (profiles use `handle`, keyword searches use `query`, Reddit uses `subreddit`). WARNING: returns RAW provider JSON — large and messy; prefer the dedicated search_* tools. Spends credits.
Paramètres, retour & exemple (2)
Paramètrespath· string · requis
exact endpoint path, e.g. '/v1/tiktok/profile' — non-allowlisted paths are rejectedparams· object · optionnel
endpoint query params, e.g. {handle:'nike'}
Exemple d'appel{"name": "fetch_social_data", "arguments": {"path": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - find_competitors≈ 243 tokens
Discover a brand's competitor / similar / adjacent brands from its domain (Claude grounded by web search). mode=competitors (default, excludes the searched company), inspiration (best relevant ads incl. it), or company. Costs a few credits for the discovery model (no ad-data charge); free inside a new account's first-brand setup.
Paramètres, retour & exemple (2)
Paramètresmode· string · optionnel · competitors | inspiration | company
'competitors' (default, excludes the searched company), 'inspiration' (best relevant ads incl. it), or 'company'domain· string · requis
the brand domain, e.g. flourish.com
Exemple d'appel{"name": "find_competitors", "arguments": {"domain": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - find_creators≈ 592 tokens
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 people, not AI actors — for influencer sourcing, UGC casting and partnership prospecting ("who should we send product to?"). About one credit per search call (platforms × queries, default 3 × 3) plus one per enriched profile; repeats inside 20 minutes are free. Then shortlist (save_to_swipefile), check a profile (instagram_profile / fetch_social_data), draft outreach (generate_text), or approve them for Partnership Ads (manage_meta_partnership_creator). marketplace:true also searches Instagram’s creator marketplace (Meta’s own creator directory: followers, badges, marketplace email) for the same niche and returns those rows beside the ranked list.
Paramètres, retour & exemple (8)
Paramètreslimit· number · optionnel
creators to return, 1–30 (default 12)niche· string · requis
product category, topic or hashtag — "calorie tracker app", "matcha", "#cleanbeauty"enrich· boolean · optionnel
read follower counts for the top 6 (default true, ~1 credit each)queries· number · optionnel
query variants per platform, 1–4 (default 3); each is a paid search callplatforms· array · optionnel
default all threemarketplace· boolean · optionnel
also search Instagram’s creator marketplace (Meta’s own creator directory, free) for the same niche; needs the Meta connectorminAvgViews· number · optionnelminEngagement· number · optionnel
interactions per view, 0–1 (0.05 = 5%)
Exemple d'appel{"name": "find_creators", "arguments": {"niche": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - find_sound≈ 168 tokens
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.
Paramètres, retour & exemple (3)
Paramètresurl· string · optionnelpick· number · optionnelquery· string · optionnel
Exemple d'appel{"name": "find_sound", "arguments": {"url": "texte", "pick": 1, "query": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - find_tools≈ 473 tokens
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 means unseen and not broken). Use it the moment the user asks for something you do not see a tool for (a campaign, an ad set, a lead form, a click-to-WhatsApp ad, a report, keywords, audiences): a tool missing from your list is NEVER proof the feature is missing. Then run the tool with call_tool. A tool that is failing or needs a connector this workspace has not made is ranked last and marked, never hidden — pass onlyHealthy:true if you want those left out. Free, read-only.
Paramètres, retour & exemple (4)
Paramètresgroup· string · optionnel
limit to one group: core, research, create, channels, channel_admin, analytics, ads, files, workspacelimit· number · optionnel
how many to return (default 12, max 40)query· string · optionnel
words from the task or the tool name, e.g. "lead form", "whatsapp", "google ads keyword", "meta insights"onlyHealthy· boolean · optionnel
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 capability.
Exemple d'appel{"name": "find_tools", "arguments": {"group": "texte", "limit": 1, "query": "texte", "onlyHealthy": true}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - finish_video≈ 417 tokens
Post-process an EXISTING rendered video (its served mp4 URL) with the proven direct-response 'reviewer' finish and/or a film-grain pass — no AI model, ~30s, a couple of credits. pills=true composites a header pill (e.g. '10/10 would buy again'), a brand-accent sub-pill, and 3-4 green-check proof pills cascading in on the beat (YOU author the copy: header ≤40 chars, sub ≤34, each point ≤44 — concrete real benefits, never fabricated stats). grain=true applies a subtle camera-grain finish that makes photoreal AI renders look phone-shot ('less AI') — works alone or with pills. Returns a NEW video; the original is untouched.
Paramètres, retour & exemple (7)
Paramètressub· string · optionnel
accent sub-pill copy, ≤34 chars (usually the product/brand)grain· boolean · optionnel
default false — anti-AI film-grain finishpills· boolean · optionnel
default true — set false for a grain-only passaccent· string · optionnel
brand accent hex for the sub-pillheader· string · optionnel
header pill copy, ≤40 chars (required when pills is on)points· array · optionnel
3-4 proof points, ≤44 chars eachvideoUrl· string · requis
the served URL of the video to finish (from a previous render/job)
Exemple d'appel{"name": "finish_video", "arguments": {"videoUrl": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - fix_beat≈ 422 tokens
Surgically re-render ONE time window (1.5-8s) of an existing rendered video and splice it back on the VIDEO TRACK ONLY — the rest of the video and ALL audio stay byte-identical. Use when one beat/shot is broken ('the shot at 8 seconds glitches') and a full re-render would waste the parts that worked; bills only the replacement clip's seconds (~1/3 of a full render). Do NOT pick a window covering spoken dialogue (a video-only splice under speech breaks lip-sync) — pass speechWindows to enforce this.
Paramètres, retour & exemple (7)
ParamètresdryRun· boolean · optionnel
true = return the estimated credits this fix reserves and render nothingprompt· string · requis
what the replacement footage should show — describe the shot, matching the master's stylerefImage· string · optionnel
optional product/style anchor image URLvideoUrl· string · requis
the served URL of the master video to fixendSeconds· number · requis
window end in seconds (window 1.5-8s)startSeconds· number · requis
window start in secondsspeechWindows· array · optionnel
[[start,end],...] windows with spoken lines — the fix window must not overlap these
Exemple d'appel{"name": "fix_beat", "arguments": {"prompt": "texte", "videoUrl": "texte", "endSeconds": 1, "startSeconds": 1}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - forget≈ 173 tokens
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.
Paramètres, retour & exemple (2)
Paramètresid· string · requis
the memory item id (from list_memory)brand· string · optionnel
profile id/name from list_brands, this call only
Exemple d'appel{"name": "forget", "arguments": {"id": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - format_sheet≈ 299 tokens
Make an exported sheet readable: bold the header row, FREEZE it so it stays visible while scrolling, and auto-size the columns so nothing is cut off. Worth calling right after create_sheet — a raw export with unsized columns and a header that scrolls away is the difference between a spreadsheet someone reads and one they close. Changes no cell VALUE, so it is never gated. The defaults do all three on the first tab; pass `tab` to pick another, freezeRows:0 to skip freezing, boldHeader:false or autoResize:false to skip those.
Paramètres, retour & exemple (6)
Paramètrestab· string · optionnel
tab title or numeric sheetId (default: the first tab)sheetUrl· string · optionnelautoResize· boolean · optionnelboldHeader· boolean · optionnelfreezeRows· number · optionnel
how many top rows to freeze (default 1, 0 = none)spreadsheetId· string · optionnel
Exemple d'appel{"name": "format_sheet", "arguments": {"tab": "texte", "sheetUrl": "texte", "autoResize": true, "boldHeader": true, "freezeRows": 1, "spreadsheetId": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - generate_avatar≈ 612 tokens
ANIMATE A PHOTO so it talks: the presenter in `image` says `script` word for word, a separate voice lip-synced onto the still. Use it ONLY when the user asks for an animated photo / talking photo / lip-sync look by name; for any other talk-to-camera or spokesperson ask use generate_video with `speak` (the person filmed saying it, which reads as real footage). About 1-3 min, holds the pose steady, 480p/720p. The per-second credit price is in hermoso_capabilities (avatarEngines); dryRun:true returns this exact job's hold without rendering. Blocks until done where the host allows, else returns a job id to poll with get_job. Requires canAvatar. Spends credits; a refusal before rendering costs nothing.
Paramètres, retour & exemple (9)
Paramètresseed· integer · optionnel
fixed seed, only for an engine other than standardimage· string · requis
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 any claim against Hermoso (hermoso.ai/terms). Paid plan only; recorded.voice· string · optionnel
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 onedryRun· boolean · optionnel
return the credits this exact job would hold, without renderingengine· string · optionnel
leave out for the standard engine; only an engine listed in hermoso_capabilities avatarEngines is acceptedprompt· string · optionnel
motion direction, only for an engine other than standard that hermoso_capabilities listsscript· string · requis
the words the avatar speaksresolution· string · optionnel
'720p' (default) or '480p'acceptQueue· boolean · optionnel
only for an engine hermoso_capabilities marks oneAtATime: wait in line
Exemple d'appel{"name": "generate_avatar", "arguments": {"image": "texte", "script": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - generate_image≈ 1504 tokens
Use this when the user asks for an image ad, ad creative, product photo or social post graphic for their business or product. It renders a finished image on a real image model, on their saved brand (product photos, logo), and returns a URL ready to post. For a video, use render_ad. refImages (paths or URLs) composite a real product into the scene. MULTI-BRAND: hydration uses the SAVED brand; for a different brand (a fresh draft_brand) pass its own productImages/logo as refImages with useBrand:false, or the wrong product lands in the shot. Product photos also pick the ENGINE (the compositing model), so a named `model` is honoured only when no references ride; raw:true or useBrand:false keeps it. model = a catalog id from hermoso_capabilities (omit for the default). A PRODUCT IN A PERSON’S HANDS, or a garment on them: name an id marked `needsRefs` with a `refsMax`; person photo first, then up to three product/garment photos. It EDITS THE PHOTOGRAPH: the person is RE-POSED (stance and hands change; face, clothing, setting and light are kept), so it is the wrong tool when the rest must stay untouched. RAW MODEL ACCESS: raw:true dispatches your prompt to the model BYTE-IDENTICAL — no rewriting, no appended guidance, no negative prompt, no brand references attached on your behalf. Credits, the durable delivery of the finished asset and the per-model validation are unchanged. Seconds. Spends credits.
Paramètres, retour & exemple (10)
Paramètresraw· boolean · optionnel
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 preamble) and NO saved-brand product photos are attached, so the model you name is the model that renders. Use it to drive the raw catalog; leave it off for an on-brand ad. Billing, the durable Library landing and per-model validation are unchanged.mask· string · optionnel
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 which it read. The mask guides rather than stencils, so the change can blend a little past its edge. Runs on gpt-image-2.5 (`refs.mask` in hermoso_capabilities): leave `model` empty or name it; another named model is refused, free. Needs refImages; keeps the source image's frame, so aspectRatio is not applied.model· string · optionnel
image model id from hermoso_capabilities. A model whose `refs.mode` is "edit" there (gpt-image-2.5) takes your refImages on ITS OWN editor, up to its `refs.max`, instead of the default compositorprompt· string · optionnel
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 AND IT IS OPTIONAL — leave it out and the pose is built for you. If you do write one, DESCRIBE THE POSE ("she holds the bottle upright in her right hand at chest height, label to camera"); do NOT phrase it as a swap ("replace the mug with the bottle"), which is REFUSED for free, because the product then comes out the size of whatever it replaced — a 30ml bottle rendered mug-sized in testing.fixLabel· boolean · optionnel
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 ONLY if it came out wrong (a label that is already right costs only the check, a credit or two; a re-print adds about ten). The reply says whether the label was checked, fixed or left as rendered (`labelPass`). Pass false when the user wants the packaging left exactly as generated: nothing is checked or re-printed.useBrand· boolean · optionnel
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 renderimageSize· string · optionnel
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 defaultrefImages· array · optionnel
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 hands or a virtual try-on — THE ORDER IS THE CONTRACT AND IT IS NOT A COMPOSITE: refImages[0] is the PERSON photo, and the rest (up to `refsMax` minus one) are the product or garment photos. Reversed, you get the product wearing the person. A 4th product is dropped and the reply says so. Using a real person’s face, likeness or voice confirms you are them or have their consent, take full, unlimited responsibility for its use and will cover any claim against Hermoso (hermoso.ai/terms). Paid plan only; recorded.aspectRatio· string · optionnel
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 before anything is chargedlogoPlacement· string · optionnel · auto | overlay | in_scene | none
overlay: real logo file laid flat. in_scene: on something in the scene, from the file, checked. auto (default): from the prompt
Exemple d'appel{"name": "generate_image", "arguments": {"raw": true, "mask": "texte", "model": "texte", "prompt": "texte", "fixLabel": true, "useBrand": true}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - generate_music≈ 148 tokens
RAW music: describe it (genre, mood, instruments, tempo), get an instrumental MP3. Flat fee: explainerMusicCredits in hermoso_capabilities.
Paramètres, retour & exemple (1)
Paramètresprompt· string · requis
the music in words, e.g. 'lo-fi jazz, brushed drums, 80 bpm'
Exemple d'appel{"name": "generate_music", "arguments": {"prompt": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - generate_text≈ 370 tokens
Text generation against the writing-model catalog (Claude, Gemini, GPT, Llama, DeepSeek…) — ad copy, hooks, scripts, rewrites, brainstorms. Prompt-only, no ad assembly (for a finished on-brand creative use plan_ad -> render_ad). BY DEFAULT the model answers as a marketing copywriter (a short house system prompt is applied, which is what you want for ad copy); pass raw:true for a plain, unstyled answer from the model itself with NO system prompt at all. model = a writing-model id from hermoso_capabilities (omit for the default Claude orchestrator). Paid (a credit or two by length).
Paramètres, retour & exemple (3)
Paramètresraw· boolean · optionnel
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, a plain question). Default false: the copywriter framing is applied.model· string · optionnel
a writing-model id from hermoso_capabilities (a Claude / Gemini / GPT / Llama / DeepSeek id) — omit for the defaultprompt· string · requis
the writing task / question
Exemple d'appel{"name": "generate_text", "arguments": {"prompt": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - generate_video≈ 2896 tokens
Use this when the user asks for a video clip, b-roll or a shot from a description, or names a video model. For a finished video ad, Reel, TikTok ad or UGC video for their business, use render_ad. It renders one continuous clip on a real video model and returns its mp4 URL. A clip runs up to the model’s longest listed duration (the longest takes a multi-beat 30s spot in ONE take with native audio), so never assume an 8–10s cap or stitch what fits one clip; durationSeconds must be one of the model’s durations in hermoso_capabilities. NAME A MODEL in `model` to get it: an unnamed render uses a narrower auto-pool. 1–3 min. refImage anchors the first frame; ttsScript adds a voiceover. AUDIO IS NOT FREE BY DEFAULT: a clip with no audio of its own gets a music bed composed and CHARGED on top (see musicMood and audio), which can cost as much as a cheap draft clip. refVideo (a clip URL) EDITS that clip instead, keeping its canvas and length. RAW MODEL ACCESS: by default small guards are added (label safety with no reference image, a negative prompt where the model takes one, reference lines) and hex colours become names; raw:true dispatches your prompt to the model BYTE-IDENTICAL — no rewriting, no appended guidance, no negative prompt, no brand references attached on your behalf. Credits, the durable delivery of the finished asset and the per-model validation are unchanged. Spends credits (blocked on the Starter plan).
Paramètres, retour & exemple (25)
Paramètresraw· boolean · optionnel
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 want the model's own interpretation rather than Hermoso's render craft. Two vendor-required fixes still apply: extra @ImageN tokens are dropped and an over-long prompt is trimmed at a sentence (on Veo it is refused with the length and the cap instead). Billing, durable delivery and per-model validation are unchanged.loop· boolean · optionnel
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.audio· boolean · optionnel
default true. false = render SILENT: no native model audio, no music bed, and no bed charge held or billed. This is the ONLY way to decline the automatic bed (see musicMood) — leave it alone for anything that should have sound, and do not combine it with ttsScript.model· string · optionnel
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 tests) is seedance-2.5 for product, lifestyle, a person talking to camera and 30s single takes (cheaper engines broke the product there). A model the user picks is kept; never switch itshots· array · optionnel
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 that model’s shotPromptMaxChars (Kling 3.0: 512) is refused free, never cut.speak· string · optionnel
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 saying it in their own voice, matched to who they are, and the length follows the words (leave durationSeconds out). `prompt` is then the staging (e.g. "natural", "walking in a park"). Real footage, not an animated photo; generate_avatar is the animated-photo look, only when asked for by nameangles· boolean · optionnel
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 portrait alone, and each extra view adds about 25 s before the render starts, so leave it off unless asked. Views are skipped when the face library is busy, and the render always goes ahead on the portrait.extend· boolean · optionnel
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 cut. A named model (any i2v) renders it.prompt· string · optionnel
the video prompt / shot description (for a refVideo edit, this is the transformation instruction); optional with `shots`creator· string · optionnel
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 portrait rides first among the references as the on-camera person, with their saved consent, exactly as render_ad casts them; a real person saved from a photo keeps their real face on camera. An unknown name is refused by name, nothing charged.endImage· string · optionnel
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 by name, nothing charged.refImage· string · optionnel
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 any claim against Hermoso (hermoso.ai/terms). Paid plan only; recorded.refVideo· string · optionnel
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 `sourceLength`, its LENGTH (their `durations` are the per-second price ladder; a durationSeconds sent is reported as unused). Trim the source to change the length. Omit `model` for the default editor, or name a model whose videoEdit is true in hermoso_capabilities (a named model that cannot edit is refused, nothing charged); refImage rides along as the look of what the edit adds. With extend:true this is instead the clip to EXTEND. Omit to generate a fresh clip.ttsVoice· string · optionnel
voice name, e.g. Rachel / GeorgefaceRoute· string · optionnel · face_lane
'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 after a render said it could not use that face, and send the same request again; it then renders through Hermoso's face library at the normal price. Never set it on your own. The other options are a different `model` or one of the user's saved `creator`s.musicMood· string · optionnel
the mood of the music bed (upbeat / calm / warm / epic / tense / playful / elegant / hype / chill / dramatic; default warm). It does not decide WHETHER there is one: a clip with no audio track (a "silent" model in hermoso_capabilities, or one that came back mute) gets a bed composed and CHARGED automatically at explainerMusicCredits. audio:false = silent, no bed, no charge.refImages· array · optionnel
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.promptAddressed`, name them Image 1, Image 2… in order. minimax-h3-max-ref takes 9 and keeps each as a reference, not a first frame.ttsScript· string · optionnel
voiceover script to speakcameraMove· string · optionnel · orbit | orbit_left | orbit_half | orbit_full | rise | crane_up | push_in | pull_back
A named camera move around the still in refImage (orbit = a quarter turn, the default; orbit_half / orbit_full = a half turn / a turntable). Only minimax-h3-max-camera renders one: with model omitted it is picked; any other named model is refused, nothing charged. Needs refImage.extendMode· string · optionnel · cut | continue
with extend: 'continue' = the SAME take past its last frame, only when asked seamless (models with extend in hermoso_capabilities).resolution· string · optionnel · 480p | 720p | 1080p | 4k
'1080p' default; '480p'/'720p' cheaper drafts, '4k' premium (more credits). A model renders only the tiers in its own `resolutions` list in hermoso_capabilities; a tier it lacks renders at its best one without a word, so check before promising 1080p or 4k.aspectRatio· string · optionnel
default '9:16'interactionId· string · optionnel
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 · optionnel
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 ask than the model’s longest clip is REFUSED, nothing charged (never quietly truncated). Past ~15s NAME a long-clip model in `model`. For a longer spot use plan_ad with durationSeconds then render_ad, which stitches acts (on a 15s model, 40s = 15+15+10).cameraTrajectory· array · optionnel
Your own ordered camera path, 2 to 12 keyframes, for the camera-controls model only (same rule as cameraMove; overrides it). The first pose is held until its time and the last pose is held to the end. A value outside these bounds is refused by name, nothing charged.
Exemple d'appel{"name": "generate_video", "arguments": {"raw": true, "loop": true, "audio": true, "model": "texte", "shots": [], "speak": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - generate_voice≈ 390 tokens
RAW text-to-speech from the voice-model catalog: speak a script in a chosen voice and return the served MP3 URL. For a standalone voiceover / narration clip — NOT for adding audio to a video (render_ad and generate_video voice their own spots; change_voice re-voices a finished clip). engine picks the voice model (default 'seed-audio'; also 'eleven-v4' (Eleven v4; the retired id 'eleven-v3' still answers, on v4), 'minimax-speech', 'kokoro'); voice is a preset name from that engine (see hermoso_capabilities -> voice engines) — a name that engine does not have is REFUSED for free with its real list, and a few engines generate their own voice and take no preset at all (the reply says which voice actually spoke). Paid (a couple of credits by length; ≤900 characters).
Paramètres, retour & exemple (3)
Paramètrestext· string · requis
the script to speak (≤900 characters)voice· string · optionnel
a voice preset from the chosen engine (e.g. 'Aria'/'George' on eleven-v4, 'stokie_en' on seed-audio) — omit for the engine defaultengine· string · optionnel
voice-engine id: 'seed-audio' (default), 'eleven-v4' (Eleven v4), 'minimax-speech', or 'kokoro' — listed in hermoso_capabilities
Exemple d'appel{"name": "generate_voice", "arguments": {"text": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - get_brand≈ 204 tokens
What Hermoso ALREADY KNOWS for the active profile: the same saved profile (products, logos, palette, positioning) + learned memory the web Studio uses. Call it when you need to know whether a brand is on file: if hasBrand is true you can omit brand everywhere; if false, onboard with draft_brand. Not a required first step before a render: the create tools read the saved brand by themselves. 0 credits.
Paramètres, retour & exemple (1)
Paramètresbrand· string · optionnel
profile id/name from list_brands, this call only
Exemple d'appel{"name": "get_brand", "arguments": {"brand": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - get_drive_file≈ 158 tokens
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.
Paramètres, retour & exemple (1)
ParamètresfileId· string · requis
the Drive file id (from list_drive_files)
Exemple d'appel{"name": "get_drive_file", "arguments": {"fileId": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - get_job≈ 198 tokens
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).
Paramètres, retour & exemple (1)
Paramètresid· string · requis
the job id, e.g. job_xxx
Exemple d'appel{"name": "get_job", "arguments": {"id": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - get_linkedin_lead≈ 194 tokens
One LinkedIn lead by id (from list_linkedin_leads), with every answer named by field. Personal data — show, never republish. Read-only, free.
Paramètres, retour & exemple (3)
ParamètresleadId· string · requispageId· string · optionnel
the company Page that owns the form — from list_linkedin_pages; omit when one Page is sharedadAccountId· string · optionnel
read forms owned by an AD ACCOUNT instead of a Page
Exemple d'appel{"name": "get_linkedin_lead", "arguments": {"leadId": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - get_onedrive_file≈ 163 tokens
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.
Paramètres, retour & exemple (1)
ParamètresfileId· string · requis
the OneDrive item id (from list_onedrive_files)
Exemple d'appel{"name": "get_onedrive_file", "arguments": {"fileId": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - get_post_refill≈ 242 tokens
Show AUTOPILOT POSTING (autoposting, the posting refill) for this profile — "is autopilot on?", "what is waiting for review?": whether it is on, its mode (review = each batch of fresh posts waits as drafts for approval; auto = fresh posts are scheduled straight away), WHICH CHANNELS it posts to with each one’s posts per day and posting times, roughly what it costs a day, when it next runs, how many posts are queued, the DRAFTS waiting for approval (id, time, channels, caption, the new image or video), and the notes it learned from reviews. It also names the channels that CANNOT be posted to and why. Read-only, free.
Paramètres, retour & exemple (0)
Exemple d'appel{"name": "get_post_refill", "arguments": {}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - get_settings≈ 139 tokens
Read this account's app settings — the LANGUAGE Hermoso writes ads, copy and answers in, the app appearance (theme), and whether the weekly competitor-watch email is on. Same settings as the web app's Settings pane. Read-only, free.
Paramètres, retour & exemple (0)
Exemple d'appel{"name": "get_settings", "arguments": {}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - get_skill≈ 150 tokens
Load a bundled skill’s full SKILL.md workflow instructions by name (from list_skills). Follow the loaded instructions to run that workflow with the other tools. Read-only, free.
Paramètres, retour & exemple (1)
Paramètresname· string · requis
bundle name from list_skills, e.g. hermoso-generate
Exemple d'appel{"name": "get_skill", "arguments": {"name": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - headline_variants≈ 545 tokens
Turn ONE finished static ad into several copies that differ ONLY in the headline, for an A/B test: same picture, product, layout, colours and every other line. Pass `image`, and either `headlines` (your own, up to 10) or `count` (default 5, max 10) to have distinct angles written for you in the saved brand's voice (never inventing numbers, prices, ratings or claims the ad or brand does not state); `brief` steers what to test. The ad's text is read first (3 credits), then one image edit per headline; each output is proofread and flagged (textCheck) if the rendered words do not match, never silently re-rendered. When the saved brand has a product photo and the ad shows that product, the photo rides with the edit and the product's label is read and checked against it: re-printed from the photo only when it came out wrong (the check alone is charged when it is right; one small extra charge finds the product first). fixLabel:false turns that off. The whole batch is priced before anything runs. Returns each headline, its angle and its image URL.
Paramètres, retour & exemple (5)
Paramètresbrief· string · optionnel
what to test, e.g. "price-led vs outcome-led" or "speak to busy parents"count· integer · optionnel
how many headlines to write when `headlines` is omitted (default 5)image· string · requis
the finished static ad: URL, Library item URL, upload_file URL or local pathfixLabel· boolean · optionnel
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)headlines· array · optionnel
your own headlines to test (up to 10); omit to have them written
Exemple d'appel{"name": "headline_variants", "arguments": {"image": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - hermoso_capabilities≈ 223 tokens
Probe what this Hermoso account can do RIGHT NOW: available image/video model ids + their credit costs (estimates: a render is charged its actual cost), aspect ratios, video durations, the recipe ids, and the canEdit/canAvatar flags. Call it when you need a specific model id, a credit cost, or a capability you are not sure of. It is NOT a prerequisite for rendering: generate_image, generate_video and render_ad all run with `model` omitted and route to the server’s own default. Read-only, free.
Paramètres, retour & exemple (0)
Exemple d'appel{"name": "hermoso_capabilities", "arguments": {}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - hermoso_credits≈ 198 tokens
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 are FREE on every plan (X is the single per-call exception). Check before kicking off paid generation; answer "does posting cost credits?" with NO. Paid plans: media models at cost + 10% (memberRate).
Paramètres, retour & exemple (0)
Exemple d'appel{"name": "hermoso_credits", "arguments": {}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - hook_variants≈ 917 tokens
HOOK MULTIPLIER: give ONE finished video ad N NEW OPENING HOOKS, N complete versions to A/B test. A planned hook replaces the first ~1.5-4 s (ends at the source's first cut there, else 3 s; hookSeconds overrides) with a new silent shot on a DIFFERENT named mechanic (list_hooks); the rest of the footage and the WHOLE original soundtrack stay, so every version keeps the source's length, and nobody in the hook talks or shows text. hooks[] adds your own openings: {url} a FOUND viral hook (post link or file) joined in front with the approved bridge (cut before its payoff, its own payoff sound carried across; post_edit price), {prompt} an opening described in words, {mechanic} a named one; with hooks and no count only those are made. To make the found hook's subject your product or creator first: recast_hook. Pass the video's FILE URL (a render, job result, list_library or upload_file). 1-5 versions, default 3. Refused free before billing: a source over 120 s, too short for a 1.5 s hook plus 2 s after it, unreadable, or a social post as the SOURCE (clone_video remakes someone else's ad). COST: a small planning read, then each planned version is billed like fix_beat for the hook's seconds; the reply quotes credits, and dryRun:true returns the plan and quote without rendering (pass that `plan` back to render exactly those). Returns ONE JOB PER VERSION; call get_job on each until done, never describe a version before its URL arrives. Hooks that show the product use the brand's product photo (productImage overrides; useBrand:false sends none).
Paramètres, retour & exemple (10)
Paramètresplan· ? · optionnel
the `plan` object a previous dryRun returned, to render exactly those hooks without planning againcount· number · optionnel
how many hook versions, 1-5 (default 3)hooks· array · optionnel
your own openings, one version eachnotes· string · optionnel
anything the hooks must respect, e.g. "keep it calm", "show the product in every hook"video· string · requis
the finished video to give new hooks: its served file URLdryRun· boolean · optionnel
true = return the plan and the quote, render nothinguseBrand· boolean · optionnel
false = send no brand name or product photo (for a video that is not this workspace brand’s)resolution· string · optionnel · 480p | 720p | 1080p
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 first seconds). dryRun quotes whichever you pick.hookSeconds· number · optionnel
where the CURRENT hook ends, in seconds (1.5-4). Omit to use the first shot cut.productImage· string · optionnel
product photo URL used as a reference in hooks that show the product (defaults to the workspace brand’s first product photo)
Exemple d'appel{"name": "hook_variants", "arguments": {"video": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - import_from_cloud≈ 541 tokens
Pull the files in a Google Drive or OneDrive FOLDER into this profile's Library, so they can be used like anything rendered here — published, scheduled, cloned, used as a product photo or a reference. Hermoso downloads each file with the user's own connected account (a Drive/OneDrive file is not public, so this is the only way in) and stores a durable Hermoso url for each. Give `folderId` from list_drive_files / list_onedrive_files with onlyFolders — omit it for the root. GOOGLE DRIVE ONLY SHOWS WHAT THE USER HANDED OVER: our Drive scope is `drive.file`, so Hermoso can see the files and folders it created plus the ones the user picked with the Google picker in the app, and NEVER their whole Drive. Picking a FOLDER hands over the folder alone, not the files in it, so if a folder comes back empty the reply carries a link that opens the picker inside it, where the user selects every file at once (Cmd+A / Ctrl+A); give them that link. OneDrive has no such limit. SUBFOLDERS ARE NOT WALKED and Google-native docs (Docs/Sheets/Slides) have no file to download: both are reported back BY NAME rather than silently dropped, along with anything too large or unreadable, so you can tell the user exactly what did and did not come across.
Paramètres, retour & exemple (3)
Paramètreslimit· number · optionnel
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.folderId· string · optionnel
the folder to import, from list_drive_files / list_onedrive_files (onlyFolders:true). Omit for the root of the drive.provider· string · requis · drive | onedrive
which cloud — `drive` is Google Drive, `onedrive` is Microsoft OneDrive
Exemple d'appel{"name": "import_from_cloud", "arguments": {"provider": "drive"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - invite_member≈ 207 tokens
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.
Paramètres, retour & exemple (3)
Paramètresrole· string · optionnel · member | admin
default memberemail· string · requis
the invitee’s emailconfirm· boolean · optionnel
REQUIRED true — this invites a real person
Exemple d'appel{"name": "invite_member", "arguments": {"email": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - leave_connector≈ 284 tokens
On a connector that several teammates can contribute their OWN account to (see list_connectors — the row reports multiContributor), remove YOURS from this profile: your stored credential is dropped and the accounts you shared stop being shared. Your teammates' accounts on the same connection keep working, and nothing changes at the provider — reconnecting in a browser shares again. Use this instead of disconnect_connector when the connection is not yours to remove: disconnect_connector revokes the grant at the provider and only the person who created the connection may call it.
Paramètres, retour & exemple (1)
Paramètresprovider· string · requis
provider id exactly as list_connectors reports it, e.g. "linkedin", "tiktok_ads", "meta"
Exemple d'appel{"name": "leave_connector", "arguments": {"provider": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_brands≈ 188 tokens
List every profile on this account (id + name; a profile is a brand, a creator or a personal workspace) and which one this connection acts on, PLUS any profile another account shared with you. Call this, then use_brand to switch; a SHARED profile switches the same way (pass its name or the profile id printed here). If a profile looks empty when the app shows it full, you are acting on a different one: call this first. Read-only, free.
Paramètres, retour & exemple (0)
Exemple d'appel{"name": "list_brands", "arguments": {}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_business_locations≈ 276 tokens
List the Google Business Profile listings SHARED WITH THIS PROFILE — id, title, address, website and Maps link. These are the only listings anything here can post to or read: one Google login often manages several businesses (an agency manages its clients’), and the user ticks which of them belong to this profile. Call this before posting whenever more than one is shared and let the USER pick: a Post on the wrong storefront is a public mistake Hermoso will not make for them. If nothing is shared, ask the user to choose — list_connector_accounts("google_business") then set_connector_accounts — and never name or guess a listing. Read-only, 0 credits. Needs Google Business Profile connected (Settings > Connectors > Google Business Profile).
Paramètres, retour & exemple (0)
Exemple d'appel{"name": "list_business_locations", "arguments": {}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_connector_accounts≈ 401 tokens
Show every identity a connected account can act as, and which ones this PROFILE is currently allowed to use — Facebook Pages + Instagram + Meta ad accounts, Google Ads customers, LinkedIn company Pages (and the personal profile), Pinterest ad accounts, Microsoft Advertising accounts. One person often administers several; only the ticked ones can be posted to or spent from. Call this before set_connector_accounts, and let the USER pick — never guess. Providers: tiktok, x, youtube, threads, bluesky, telegram, reddit, pinterest, instagram, meta, google_ads, linkedin, pinterest_ads, linkedin_ads, reddit_ads, apple_ads, microsoft_ads, google_business, google_analytics, snapchat_ads, x_ads, tiktok_ads, google_tag_manager, google_search_console, bing_webmaster. Read-only, free.
Paramètres, retour & exemple (1)
Paramètresprovider· string · requis · tiktok | x | youtube | threads | bluesky | telegram | reddit | pinterest
which connector’s accounts to list
Exemple d'appel{"name": "list_connector_accounts", "arguments": {"provider": "tiktok"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_connectors≈ 257 tokens
List the third-party accounts connected to this workspace (Meta, Google Ads, Google Drive/Sheets/Docs, YouTube, LinkedIn, OneDrive, Slack, …) — provider, status and the connected account label — PLUS which providers are available to connect. IT ALSO FLAGS A CONNECTION WHOSE PERMISSIONS ARE OUT OF DATE: a provider writes its granted permission set into the token at consent time, so a connection authorized before a permission was approved does not carry it and never will — those calls are refused by the provider and no retry or wait can change it. Check this FIRST when a connected provider starts refusing things. The fix is a browser: the user reconnects under Workspace > Connectors. Read-only, free.
Paramètres, retour & exemple (0)
Exemple d'appel{"name": "list_connectors", "arguments": {}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_creators≈ 433 tokens
List this workspace’s SAVED CREATORS — the reusable on-camera cast (AI creators made here, a person pulled from a social profile, a consented photo upload). Read-only, FREE. Each entry gives the name, the PORTRAIT URL, where the portrait came from and whether a real person’s likeness consent is on file, how many extra pose plates exist, and any chosen or cloned voice. TO PUT ONE IN A FINISHED AD, pass their id or name as render_ad’s `creator` — that casts them for the whole spot (and skips the character-portrait render, so it costs less than not casting anyone). THE PORTRAIT URL IS THE REUSE HANDLE for the raw lanes — pass it as generate_avatar’s `image` (a talking clip of them), generate_video’s `refImage` (they star in the scene), recast_motion’s `image` (they perform a reference clip’s motion), or generate_image’s `refImages`. CALL THIS BEFORE OFFERING TO GENERATE A NEW PERSON: re-casting somebody the workspace already has keeps the SAME face across every ad, while a fresh person costs credits and breaks that continuity. An empty answer means the workspace genuinely has no cast yet — say so and offer generate_avatar / save_creator, never invent a roster.
Paramètres, retour & exemple (2)
Paramètreslimit· number · optionnel
max creators to return (default 24)gender· string · optionnel · female | male
filter the PRESET creators by gender (the saved cast is never filtered)
Exemple d'appel{"name": "list_creators", "arguments": {"limit": 1, "gender": "female"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_drive_files≈ 416 tokens
List the Google Drive files & folders Hermoso can reach: the ones it created plus any the user picked with the Google file picker in the app (drive.file, never their whole Drive). This is how you find a picked file's id. Filter by query (name contains …), folderId, or onlyFolders:true; paginate with pageToken. Read-only. A FOLDER: picking a folder hands over the folder alone, not its files, so a folder can list empty or partly; the reply then carries `pickUrl`, an app link that opens the picker inside that folder (Cmd+A / Ctrl+A selects every file and subfolder). Give the user that exact link, then list again. recursive:true walks every subfolder Hermoso can see (each row has its `path`) and reports picked files whose folder it cannot see.
Paramètres, retour & exemple (7)
Paramètresquery· string · optionnel
only files whose name contains thisfolderId· string · optionnel
list the contents of this folder idpageSize· number · optionnel
rows per page (1–200, default 50)pageToken· string · optionnel
cursor from a previous callrecursive· boolean · optionnel
with folderId: the whole treeonlyFolders· boolean · optionnel
list folders onlyincludeTrashed· boolean · optionnel
include trashed files (default false)
Exemple d'appel{"name": "list_drive_files", "arguments": {"query": "texte", "folderId": "texte", "pageSize": 1, "pageToken": "texte", "recursive": true, "onlyFolders": true}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_errors≈ 412 tokens
The errors actually recorded against this workspace, GROUPED by fingerprint — the same failure at the same call site is one row with a hit count and first/last seen, sorted defects-first. Each row says whose side it is: `ours` (a defect worth fixing), `user` (a refusal we deliberately authored, e.g. not-connected or out-of-credits), or `unknown` (a vendor 4xx we cannot attribute — never guessed). Free text, tokens, emails and creative are redacted before anything is stored, so an input echo shows shapes and lengths, not content. Filter by surface (http/mcp/agent/job/client) or kind. Read-only, 0 credits. Scoped to your own workspace; an operator whose client carries the admin key sees every account.
Paramètres, retour & exemple (4)
Paramètreskind· string · optionnel · ours | user | unknown
'ours' = a defect; 'user' = a refusal we authored; 'unknown' = we could not telllimit· number · optionnel
how many groups to return (default 50, max 200)since· string · optionnel
ISO timestamp — only groups last seen at or after thissurface· string · optionnel · http | mcp | agent | job | client
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)
Exemple d'appel{"name": "list_errors", "arguments": {"kind": "ours", "limit": 1, "since": "texte", "surface": "http"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_hooks≈ 509 tokens
The curated menu of VISUAL scroll-stop HOOKS (how an ad opens) and SETTINGS (where it is staged) that plan_ad and render_ad accept, PLUS this brand's measured traction per hook. Call it before planning an ad to pick a hook deliberately instead of letting the model improvise one, and call it after publishing to see which ones are actually landing. Three things it will not do: it never recommends a hook from thin data — a verdict is SUPPRESSED below 5 measured posts and the reason is stated; it never compares across channels; and it reports hooks you have NEVER TRIED as a fact, not as advice, because 'you haven't tried this' is an observation and 'you should' would be a verdict drawn from zero data. A hook marked unusable in this brief says WHY (an on-screen-text hook cannot ride an authentic/UGC render, which carries zero on-screen text). Read-only, 0 credits.
Paramètres, retour & exemple (4)
Paramètrestier· string · optionnel · luxury | premium | drugstore
product tier, used with category — changes the FINISH of the room, never the room. Default premium.channel· string · optionnel
restrict the performance half to one channel (facebook, instagram, threads, x, linkedin, youtube, tiktok, reddit, pinterest)category· string · optionnel
the product category (e.g. 'skincare serum', 'protein powder', 'sunglasses') — returns the setting our Location x Tier matrix puts that category in, with the reasonauthentic· boolean · optionnel
true if the planned ad is an authentic/UGC/creator-register render — on-screen-text hooks are then reported unusable, with the reason
Exemple d'appel{"name": "list_hooks", "arguments": {"tier": "luxury", "channel": "texte", "category": "texte", "authentic": true}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_jobs≈ 108 tokens
List the most recent render jobs + how many are currently running, so you can report on or resume in-flight work.
Paramètres, retour & exemple (0)
Exemple d'appel{"name": "list_jobs", "arguments": {}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_library≈ 212 tokens
Browse this workspace's Library — every image/video generated in the Studio, newest first (the same Library the web app shows). Returns served URLs you can open directly or hand to fetch_asset for a download link, plus each asset's id (L0, L1…, which plan_ad and render_ad `inserts` take), kind, model, and age. Free, read-only.
Paramètres, retour & exemple (2)
Paramètreskind· string · optionnel · image | video | all
filter by asset kind (default 'all')limit· number · optionnel
max assets to return (default 20, max 60)
Exemple d'appel{"name": "list_library", "arguments": {"kind": "image", "limit": 1}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_linkedin_lead_events≈ 191 tokens
The lead events LinkedIn has PUSHED to Hermoso for this brand (new lead / deleted lead, with the form and the lead id), newest first. Empty means none have arrived, not that none exist — list_linkedin_leads reads every lead regardless, and subscribe_linkedin_leads is what starts delivery. Read a lead’s answers with get_linkedin_lead. Read-only, free.
Paramètres, retour & exemple (1)
Paramètreslimit· number · optionnel
Exemple d'appel{"name": "list_linkedin_lead_events", "arguments": {"limit": 1}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_linkedin_lead_forms≈ 248 tokens
The LEAD GEN FORMS a LinkedIn company Page or ad account owns — id, name, state, version and the fields each one asks for (firstName, email, company …). Forms are created in Campaign Manager or on the Page; this API reads them and cannot create one. If it answers that the connection must be reconnected, say exactly that: the lead-sync permission is granted at authorise time. Read-only, free.
Paramètres, retour & exemple (2)
ParamètrespageId· string · optionnel
the company Page that owns the form — from list_linkedin_pages; omit when one Page is sharedadAccountId· string · optionnel
read forms owned by an AD ACCOUNT instead of a Page
Exemple d'appel{"name": "list_linkedin_lead_forms", "arguments": {"pageId": "texte", "adAccountId": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_linkedin_lead_subscriptions≈ 196 tokens
The lead notification webhooks registered on a LinkedIn Page or ad account, with the id delete_linkedin_lead_subscription takes. Read-only, free.
Paramètres, retour & exemple (3)
ParamètrespageId· string · optionnel
the company Page that owns the form — from list_linkedin_pages; omit when one Page is sharedleadType· string · optionneladAccountId· string · optionnel
read forms owned by an AD ACCOUNT instead of a Page
Exemple d'appel{"name": "list_linkedin_lead_subscriptions", "arguments": {"pageId": "texte", "leadType": "texte", "adAccountId": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_linkedin_leads≈ 474 tokens
The LEADS a LinkedIn lead gen form collected — every response with its answers keyed by field (firstName, lastName, email, company, …), the campaign and creative that produced it, the consents ticked, and whether it was a test lead. Newest first. Filter by formId, a since/until window (ISO date or epoch ms — LinkedIn takes epoch), or testLeadsOnly. THIS IS PERSONAL DATA: show it to the user, hand it to the CRM they name, never repeat it into a post or an unrelated tool. Pass start for the next page. Read-only, free.
Paramètres, retour & exemple (10)
Paramètreslimit· number · optionnel
per page, max 100since· string · optionnel
ISO date or epoch millisecondsstart· number · optionnel
offset for the next pageuntil· string · optionnelformId· string · optionnel
only this form (from list_linkedin_lead_forms)pageId· string · optionnel
the company Page that owns the form — from list_linkedin_pages; omit when one Page is sharedleadType· string · optionnel · SPONSORED | COMPANY | EVENT | ORGANIZATION_PRODUCT
defaults by owner: SPONSORED for an ad account, COMPANY (organic Page form) for a Page; EVENT for event forms. LinkedIn refuses SPONSORED on a Page owneradAccountId· string · optionnel
read forms owned by an AD ACCOUNT instead of a PageformVersion· number · optionnel
default 1testLeadsOnly· boolean · optionnel
true returns ONLY test submissions
Exemple d'appel{"name": "list_linkedin_leads", "arguments": {"limit": 1, "since": "texte", "start": 1, "until": "texte", "formId": "texte", "pageId": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_linkedin_pages≈ 218 tokens
List the LinkedIn COMPANY PAGES the connected account administers — id, name and the role held on each. ALWAYS call this before post_to_linkedin_page when there is more than one Page: publishing to the wrong company Page is a public mistake and Hermoso never chooses for the user. If it comes back empty, the account holds no Page admin role, or LinkedIn has not granted this app the organization scopes — say that plainly rather than guessing an id. Read-only, free.
Paramètres, retour & exemple (0)
Exemple d'appel{"name": "list_linkedin_pages", "arguments": {}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_memory≈ 184 tokens
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.
Paramètres, retour & exemple (2)
Paramètreslimit· number · optionnel
max items (default 50, max 200)category· string · optionnel
filter to one bucket (Brand/Audience/Taste/Do/Don’t/Preference)
Exemple d'appel{"name": "list_memory", "arguments": {"limit": 1, "category": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_meta_pages≈ 159 tokens
List the Facebook Pages (with any linked Instagram business account) and ad accounts on the connected Meta account — use before post_to_meta / create_meta_campaign to pick the target. Requires the user to have connected Meta (Settings > Connectors > Meta); returns a connect hint if not.
Paramètres, retour & exemple (0)
Exemple d'appel{"name": "list_meta_pages", "arguments": {}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_meta_posts≈ 545 tokens
List the connected Facebook Page's or Instagram account's OWN existing posts — id, caption, permalink, publish date and format. THIS IS THE TOOL THAT GETS YOU THE postId every other Meta read needs: meta_post_insights, list_meta_comments and manage_meta_post all require one. Use it for "how did our last few posts do", to find a post the user describes loosely, or before backfill_posts. Pass target:'instagram' for the linked IG account (Stories are excluded — Meta's media edge does not return them); Facebook hides unpublished drafts unless you ask for them. Only ever reads a Page the brand has connected. Read-only, 0 credits.
Paramètres, retour & exemple (7)
Paramètresbrand· string · optionnel
WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CALL ONLY. A name that matches no profile, or two, is REFUSED and nothing is done.limit· number · optionnel
how many posts (default 25, max 100)cursor· string · optionnel
paging cursor returned by a previous callpageId· string · optionnel
which connected Page — omit when the profile has only onetarget· string · optionnel · facebook | instagram
default facebook; 'instagram' reads the Page's linked IG business accountaccount· string · optionnel
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 Page-linked account.includeUnpublished· boolean · optionnel
Facebook only — also return unpublished drafts (hidden by default)
Exemple d'appel{"name": "list_meta_posts", "arguments": {"brand": "texte", "limit": 1, "cursor": "texte", "pageId": "texte", "target": "facebook", "account": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_onedrive_files≈ 244 tokens
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.
Paramètres, retour & exemple (5)
Paramètresquery· string · optionnel
search — only items whose name matches thisfolderId· string · optionnel
list the contents of this folder idpageSize· number · optionnel
rows per page (1–200, default 50)pageToken· string · optionnel
cursor from a previous callonlyFolders· boolean · optionnel
list folders only
Exemple d'appel{"name": "list_onedrive_files", "arguments": {"query": "texte", "folderId": "texte", "pageSize": 1, "pageToken": "texte", "onlyFolders": true}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_pinterest_boards≈ 204 tokens
List the boards on the user’s connected Pinterest account — id, name, privacy and pin count. ALWAYS call this before post_to_pinterest and let the USER pick: Pinterest requires a board and Hermoso never chooses one for them. Read-only, 0 credits. Needs Pinterest connected (Settings > Connectors > Pinterest).
Paramètres, retour & exemple (1)
Paramètresprivacy· string · optionnel · ALL | PUBLIC | PROTECTED | SECRET
filter by board privacy; default is everything the connection can see
Exemple d'appel{"name": "list_pinterest_boards", "arguments": {"privacy": "ALL"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_playbooks≈ 224 tokens
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.
Paramètres, retour & exemple (2)
Paramètresfull· boolean · optionnel
true to return every hook/angle/play in the text, not just the headline countslimit· number · optionnel
max playbooks to return (default 25, max 100)
Exemple d'appel{"name": "list_playbooks", "arguments": {"full": true, "limit": 1}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_product_photos≈ 234 tokens
List the product photos ALREADY saved in your workspace — the brand's product library plus any app-store screens (also surfaces photos locked in your OTHER creations, since a set product lands in the shared library). FREE — returns each photo's url + label. Call it before set_product_image to see the existing photos you can reuse. Reads YOUR saved brand (pass brandId to target a specific profile — that switches this key's active profile like use_brand).
Paramètres, retour & exemple (1)
ParamètresbrandId· string · optionnel
a profile id/name from list_brands whose product library to list; omit to use the active profile
Exemple d'appel{"name": "list_product_photos", "arguments": {"brandId": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_published_posts≈ 402 tokens
List every post Hermoso has recorded publishing for this brand, newest first, across all channels — channel, permalink, caption, format, the HOOK and SUBJECT it was written to, and its measured engagement. The WHOLE history, no cap: pass the reply's nextCursor as `cursor` for older posts. Each row says how it was recorded: 'captured' (written at publish time — the hook is what the author intended) or 'backfilled' (reconstructed from the platform afterwards). A dash for engagement means the platform reported no number — NOT zero. Read-only, 0 credits.
Paramètres, retour & exemple (4)
Paramètresbrand· string · optionnel
WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CALL ONLY. A name that matches no profile, or two, is REFUSED and nothing is done.limit· number · optionnel
max posts (default 50, max 200), newest firstcursor· string · optionnel
nextCursor from a previous reply: the next, older pagechannel· string · optionnel
filter to one channel: facebook, instagram, threads, x, linkedin, youtube, tiktok, reddit, pinterest, google_business
Exemple d'appel{"name": "list_published_posts", "arguments": {"brand": "texte", "limit": 1, "cursor": "texte", "channel": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_scheduled≈ 482 tokens
Use this when the user asks what is scheduled, what already posted, or whether a post went out. Show what is queued to post and what already went out. Each fired item reports PER-CHANNEL outcomes, so you can see that (say) Instagram published and TikTok failed on the same item rather than a single misleading verdict. Past items are rebuilt from published-post records, one row per channel; they are not job runs (use list_jobs for those). THE LIST IS COMPACT so it fits in one reply: the next 25 queued and the last 15 fired, captions shortened. Pass `id` for ONE post in full (every caption and setting, which you need before reschedule_post replaces a caption map), `channel` to filter, or `upcoming` / `fired` for more rows. Read-only, 0 credits.
Paramètres, retour & exemple (5)
Paramètresid· string · optionnel
one post id from this list: returns that post in full, every caption and setting includedbrand· string · optionnel
WHICH PROFILE to list — the id or exact name from list_brands. Needed when the post lives in a profile this connection is not pinned to: a post you can CREATE in a profile must be manageable there too, without switching the whole connection. A name that matches no profile, or two, is REFUSED.fired· number · optionnel
how many already-fired posts to list, most recent last (default 15, max 200)channel· string · optionnel
only posts that include this channel, e.g. "pinterest" or "x"upcoming· number · optionnel
how many queued posts to list, soonest first (default 25, max 200)
Exemple d'appel{"name": "list_scheduled", "arguments": {"id": "texte", "brand": "texte", "fired": 1, "channel": "texte", "upcoming": 1}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_sheet_tabs≈ 232 tokens
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.
Paramètres, retour & exemple (2)
ParamètressheetUrl· string · optionnel
a Google Sheets URL — the id is extracted from itspreadsheetId· string · optionnel
the spreadsheet id (from create_sheet, or list_drive_files for one the user picked)
Exemple d'appel{"name": "list_sheet_tabs", "arguments": {"sheetUrl": "texte", "spreadsheetId": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_skills≈ 153 tokens
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.
Paramètres, retour & exemple (0)
Exemple d'appel{"name": "list_skills", "arguments": {}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_swipefile≈ 237 tokens
List this workspace's SWIPEFILE — the saved-ad research board: every named collection and the ads/creatives kept in it (advertiser, headline, body copy, media URL, platform, when it was saved, and any taste tags). The SAME board the web app's Swipefile tab shows. Use it to answer "what have we saved?", to mine the user's own taste before planning an ad, or to find a reference to remix. Read-only, free.
Paramètres, retour & exemple (2)
Paramètreslimit· number · optionnel
max ads to return (default 50, max 500)collection· string · optionnel
only list ads in this collection (by name or id) — omit for every collection
Exemple d'appel{"name": "list_swipefile", "arguments": {"limit": 1, "collection": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_team≈ 104 tokens
List the members of the current profile — email, role (admin/member) and status. Read-only, free.
Paramètres, retour & exemple (0)
Exemple d'appel{"name": "list_team", "arguments": {}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_telegram_chats≈ 309 tokens
Find the chat ids this Telegram bot can be addressed by. STATE THE LIMIT WHENEVER YOU USE IT: this is NOT the list of chats the bot belongs to — the Bot API publishes no such method — it is every chat that SENT the bot an update in the last 24 hours, which is as long as Telegram keeps an update. A channel the bot posts to every day but nobody messages will NOT appear here, and its absence means nothing at all: post to it by @username or numeric id anyway. If the bot has an outgoing WEBHOOK configured the list is empty for that reason alone (Telegram: getUpdates "will not work if an outgoing webhook is set up"), and the reply says so rather than reading as an empty account. Nothing is consumed — no update offset is confirmed, so this cannot eat the bot’s pending updates. Free, 0 credits.
Paramètres, retour & exemple (1)
Paramètreslimit· number · optionnel
how many recent updates to scan, 1–100 (default 100)
Exemple d'appel{"name": "list_telegram_chats", "arguments": {"limit": 1}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_watch_findings≈ 363 tokens
Read what the standing COMPETITOR WATCH has found — the new ads each watched brand has launched since the last check, plus the watch's own state (who is watched, when it last ran, when it runs next, and whether the last run actually succeeded). The same board the web app's Ad Spy > Watching tab renders. Use it to answer "what are our competitors running that's new?", to feed a teardown, or to save something worth keeping with save_to_swipefile. Findings marked `seed:true` are NOT new launches — the first check of a brand has nothing to diff against, so it seeds the board with what that brand is running right now; only later runs surface genuine changes. Read-only and free — it returns the stored results of past runs and never triggers a check (set_competitor_watch({runNow:true}) is what runs one).
Paramètres, retour & exemple (2)
Paramètreslimit· number · optionnel
max findings to return (default 25, max 75 — the server keeps at most 75, and at most 15 per brand)competitor· string · optionnel
only findings for this watched brand (exact name as returned in `watching`) — omit for all of them
Exemple d'appel{"name": "list_watch_findings", "arguments": {"limit": 1, "competitor": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - list_whatsapp_accounts≈ 431 tokens
The WhatsApp Business Accounts SHARED WITH THIS PROFILE and the phone numbers registered on each — the ids every other WhatsApp tool needs, plus each number’s QUALITY RATING, which is what decides how many messages Meta will let it send. Start here. A WABA with no number cannot send anything and the reply says so: Hermoso does not register or verify numbers, that is WhatsApp Manager. A business portfolio that could not be read is REPORTED rather than dropped — an account missing from this list would read as "the brand has no WhatsApp", which is a claim about their business and not about our read. TWO ACCOUNTS CAN CARRY THE SAME DISPLAY NAME — always quote the `display` field or the id when naming one to a user, never the bare name. ONLY THE ACCOUNTS SHARED WITH THIS PROFILE ARE REACHABLE. One Meta login often administers WhatsApp for several businesses, so a human ticks which belong to this profile under Settings > Connectors > Meta > Manage accounts (or set_connector_accounts(provider:"meta")); nothing else here can see or touch the rest. With none ticked every WhatsApp tool refuses and names that as the way out. With exactly ONE ticked, wabaId is optional — pass it only to disambiguate. Read-only, 0 credits (WhatsApp conversations are billed by META to the business directly, never in Hermoso credits). Needs Meta connected.
Paramètres, retour & exemple (0)
Exemple d'appel{"name": "list_whatsapp_accounts", "arguments": {}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - localize_ad≈ 454 tokens
Translate the on-image text of ONE finished static ad into other languages and keep everything else: same picture, layout, typeface, colours, logo and product. Pass `image` and `languages` (up to 5, e.g. ["Spanish", "German", "French (Canada)"]). The ad's text is read (3 credits), translated the way a native copywriter in each market would write it (brand and product names, URLs and prices kept as written), then one image edit per language; each output is proofread and flagged (textCheck) if the words do not match, never silently re-rendered. When the saved brand has a product photo and the ad shows that product, the photo rides with the edit and the product's label is read and checked against it: re-printed from the photo only when it came out wrong (the check alone is charged when it is right; one small extra charge finds the product first). fixLabel:false turns that off. Priced before it runs. For a VIDEO use dub_video.
Paramètres, retour & exemple (3)
Paramètresimage· string · requis
the finished static ad: URL, Library item URL, upload_file URL or local pathfixLabel· boolean · optionnel
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)languages· array · requis
target languages, by name
Exemple d'appel{"name": "localize_ad", "arguments": {"image": "texte", "languages": []}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - make_explainer≈ 2526 tokens
AI HOST EPISODE: format 'host_episode' = one presenter talking to camera, the camera changing every piece (frontal, three-quarter, close), the same host and set throughout, native voice, 16:9. Host = `creator` (saved or preset) or `hostImage`; words = `topic` (written for you) or `script` (verbatim). It returns a 480p DRAFT; HD (720p) is a SEPARATE call with `fromDraft` (quote it with dryRun, run it only when the user asks). Otherwise: turn a TOPIC into a finished narrated explainer video, in one of TWO LANES (`lane`). 'blocks' (the default) = 10-second VIDEO blocks, one narrated line per block, hard cuts, a music bed under the voice: an EXPLAINER renders on Gemini Omni at 720p (9:16 by default, or 16:9), a FACELESS CHANNEL video (`format:'faceless_channel'`, or channel history / kids / fairytale) on MiniMax H3 at 2K (16:9 by default) with five cuts per block. 'stills' = a picture film: writes a sectioned script, paints a BURST of pictures per section (about one every 1.5s — most of them one-detail edits of the frame before, so it reads as movement rather than a slideshow), narrates each section with TTS, holds each picture PERFECTLY STILL for its own slice of the narration (the motion is the CUT RATE — a slow move on a still shimmers), then composites the end card (and any on-screen text you asked for) with the Chrome+ffmpeg engine the ads use (text is never model-painted, so it never garbles). BURNED ON-SCREEN TEXT IS OFF BY DEFAULT — the narration carries the point and the pictures carry the story, so the film ships clean unless the user asks otherwise; `captions:true` adds held key points and `subtitles:true` adds narration-timed CAPS (see both). The stills lane is an image film WITH motion, not N video-model renders — that's what keeps it affordable. `style` picks the visual family: the default 'cinematic' is photoreal editorial; every other id is a STYLED, strictly non-photoreal look (illustrated / collage / clay / pixel …) that first renders ONE style-key image and then locks every scene to it, so the whole film holds one look. Cost at the default frame density: a ~130-credit hold for a 60s explainer on the default style, ~100 styled; `frameDensity:'lean'` roughly halves it and `'minimal'` (one picture per section) is ~30. All settle to the exact per-frame image + narration spend (a longer target = more sections = more). Takes SEVERAL minutes — one image render per frame; independent frames are painted concurrently, so it is far faster than the frame count suggests. Needs the writing model and a narration voice engine connected. NOT the tool for a short product ad — use render_ad or generate_video for those, and make_template_ad for the deterministic native formats.
Paramètres, retour & exemple (23)
Paramètreslane· string · optionnel · blocks | stills
'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 dryRun.music· string · optionnel
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 — those two are the only channels a bed is due on unasked, and it costs a small flat fee; every other channel ships dry. 'off' forces silence. 'library' takes a free curated track only, and ships dry when none is on file. NAME A MOOD (upbeat / calm / warm / epic / tense / playful / elegant / hype / chill / dramatic) or DESCRIBE it in words to compose one on ANY channel, at the same fee. hermoso_capabilities reports the exact figure as explainerMusicCredits; quote it before you turn a bed on or pick a mood.style· string · optionnel
visual style: 'cinematic' (default, photoreal); styled shortcuts editorial_collage, flat_vector, stickman, whiteboard, ink_marker, silhouette, storybook, paper_diorama, isometric, claymation, pixel_art, watercolor, fluffy_toy, low_poly, stylized_3d, studio_3d (the Kids default), mannequin; or ANY look described in words ('80s anime cel animation'), locked across every frame. Ask rather than pick silently; a styled look costs more.topic· string · optionnel
what the explainer should teach or explain — a topic or a short brief (host_episode: or pass `script`)voice· string · optionnel
narration voice name — omit for the default warm readdryRun· boolean · optionnel
true = return the estimated credits this explainer reserves (its own pricing, stopped at the hold) and render nothing. Quote it before running one; try frameDensity lean or minimal when the balance is short.format· string · optionnel · explainer | faceless_channel | host_episode
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, five hard cuts per 10s block, a whole number of blocks). Omit and a history / kids / fairytale channel is a faceless channel video. 'host_episode' = the AI host episode (see the top).script· string · optionnel
host_episode: the exact words, said verbatim and split at natural breaks into 4-30s piecescameras· array · optionnel
host_episode: the rotation, ids frontal / three_quarter / close or framings in words; one entry = one fixed camerachannel· string · optionnel · explainer | history | kids | fairytale
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 (witty chronological retelling / documentary), kids (fastest, question-first, warm teacher), fairytale (slow, atmospheric myth or folklore). Default 'explainer'.creator· string · optionnel
host_episode: the host, a saved creator or a preset by name or id (list_creators)endCard· boolean · optionnel
append the branded end card. DEFAULT FALSE — set true ONLY when the user asks for onesetting· string · optionnel
host_episode: the set in words (default: written to fit the topic)upscale· number · optionnel
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 times the cost of rendering the film itself. hermoso_capabilities reports the exact figures per length as explainerUpscaleCredits. Never turn it on unasked: quote the number and let the user choose.captions· boolean · optionnel
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` on its own burns SUBTITLES (see below), because that is what a caption is for: showing what is being said when the phone is on mute. Slim white CAPS, thin black outline, bottom safe band, no plate, no box.brandName· string · optionnel
brand name for the end card — omit to leave it unbrandedfromDraft· string · optionnel
host_episode: a finished draft's job id, to render it in HD (720p) with the same script, cameras, set and hosthostImage· string · optionnel
host_episode: a photo URL of the host instead (upload_file for a local file); a real person's face needs a paid plansubtitles· boolean · optionnel
which on-screen text, once `captions` is on. LEAVE IT UNSET (or true) for SUBTITLES — every spoken word, in order, timed to the narration; free, no extra render, no extra credits, and there is NO cue limit, so the whole film is subtitled however long it runs (at most 5 words / 32 characters a line). Set it FALSE only if the user explicitly wants section HEADINGS instead: one short summary label held over each ~7-15s section. That is NOT what is being said — it is a label about it — so it is the wrong answer to "add captions" and to anyone watching on mute. `subtitles:true` also implies `captions:true`. TIMING: each cue is anchored to that section’s REAL measured narration length and distributed inside the section by character count — exact at every section boundary, approximate to a few tenths of a second within one. It is not a word-level speech clock, so never promise frame-accurate sync.thumbnail· boolean · optionnel
host_episode: one thumbnail of the host (default true)aspectRatio· string · optionnel · 9:16 | 16:9 | 1:1 | 4:5 | 3:4
'9:16' default (a faceless channel video and a host episode default to 16:9)frameDensity· string · optionnel · standard | lean | minimal
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; 'lean' is one about every 2.5s (the longest hold that still reads as a film, ~40% of the frames and ~40% of the cost); 'minimal' is one picture about every 3.5s, the cheapest and the longest any still is ever held, and it reads close to a slideshow. Only drop below the default if the user asked for something cheaper.durationSeconds· number · optionnel
target length 20-120s (default 60); drives the section count — ~10s of narration each, 3-8 sections
Exemple d'appel{"name": "make_explainer", "arguments": {"lane": "blocks", "music": "texte", "style": "texte", "topic": "texte", "voice": "texte", "dryRun": true}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - make_insert≈ 178 tokens
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.
Paramètres, retour & exemple (7)
Paramètresimage· string · optionnelsound· string · requisvideo· string · optionnelseconds· number · optionnelsoundEnd· number · optionnelsoundStart· number · optionnelvideoStart· number · optionnel
Exemple d'appel{"name": "make_insert", "arguments": {"sound": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - make_template_ad≈ 884 tokens
Use this when the user asks for a quick, low-cost ad or post built from their words and pictures: a slideshow, carousel, iMessage or ChatGPT chat, Notes, kinetic type, myth vs fact, an app tour, or their own HTML design. Rendered from HTML, no AI model, ~30s, a couple of credits, ready to post. Presets are SHORTCUTS; 'custom' is YOUR OWN design as config.html (+ css), so no layout, type, colour or motion is unsupported. custom: { html, css?, size? ('9:16' default | '4:5' | '1:1' | '16:9' | any 'W:H' | {w,h} px), durationSeconds? (1-60 = VIDEO; CSS/SVG animation and <video> are frame-stepped, scripts stripped), slides?:[{html, css?}] (2-35 = carousel) }; {{logo}} {{brandName}} {{domain}} {{accent}} fill from the brand; images and fonts load by public https URL; notes[] lists what failed to load. YOU write preset copy: short, casual, believable, finished phrases within budget. Preset ids and their fields are listed on `config`. config.music on a VIDEO: omit for a free library bed matched to its mood when the library is stocked (hermoso_capabilities hasMusic; else only its own sound effects), 'off' for silence, or words (a mood or description) to compose one for a flat fee.
Paramètres, retour & exemple (1)
Paramètresconfig· object · requis
MUST include config.template: 'custom' or a preset id, plus its fields. PRESETS: 'slideshow' (IMAGES, TikTok photo mode / Reels 1080x1920, or size:'4:5' feed carousels; no branding): { slides:[{text, sub?, image?, blur?, background?, position?}] (2-35; words never rewritten), style? ('tiktok-classic'|'clean-minimal'|'note-style' or a look in words), textStyle?, video?:true (+ an MP4, which takes config.music like any video) }; 2 credits, +1 per slide past 5, +2 for the MP4. 'imessage-chat' (VIDEO ~15s): { thread:{contactName, messages:[{from:'them'|'me', text?, product?:{image,title,domain}}]}, theme?, endCard }. 'chatgpt-chat' (VIDEO): { question, answer (may **bold** the brand), productImage?, endCard }. 'apple-notes' (VIDEO): { title, lines[], theme?, endCard }. 'value-prop' (VIDEO ~17s): { hook ≤40ch, claims[3-5 ≤34ch], productImages[2-3], palette[], endCard }. 'static-mockup' (IMAGE): { style:'imessage'|'notes'|'card', size?:{w,h}, ...fields }. 'airdrop-carousel' (VIDEO): { brandName, products:[{image, title?}] (3-16), endCard }. 'app-ui-tour' (VIDEO): { hook?, appName, iconImage?, beats:[{screenImage, caption}] (2-6), endCard }. 'imessage-cascade' (VIDEO): { notifications:[{sender, text}] (4-8), backgroundImage?, endCard }. 'photo-grid' (VIDEO): { title?, photos:[{image, label?}] (4-9), endCard }. 'vignette' (VIDEO): { hook, lines[2-4 ≤40ch], heroImage, endCard }. 'kinetic-type' (VIDEO, own SFX): { phrases[3-6 ≤34ch], productImages?[≤4], endCard }. 'myth-vs-fact' (VIDEO with a real VOICEOVER, small extra charge): { pairs:[{myth ≤50ch, fact ≤60ch}] (2-4; [brackets] accent), endCard }, real truths only. 'carousel' (IMAGES, 5-10 branded 1080x1080): { cover:{hook?, title}, slides:[{headline, support?, stat?:{value, label}}] (3-8), cta:{headline, cta?, domain?}, productImage?, logo? }. endCard = { headline, cta, domain?, logo?, color? }; palette and fontStack optional.
Exemple d'appel{"name": "make_template_ad", "arguments": {"config": {}}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - make_thumbnail≈ 2176 tokens
Render a click-driving YOUTUBE / Shorts / Instagram THUMBNAIL or video cover through the full production pipeline (concept, casting, scene, render, tweaks, text), not a bare image prompt. Use it for any "thumbnail", "video cover" or MrBeast-style packaging ask INSTEAD of generate_image. About 9 credits per variant; the headline overlay is free. CONCEPT — open an INFORMATION GAP (the image raises a question the title answers) while staying truthful to the video. Brainstorm ≥5 concepts across the 16 frameworks (ids on `framework`; combining two is fine) before you pick; hermoso_capabilities has each one's 'realize it with' note and the emotion, overlay, font and rim-colour catalogs. THREE GATES, all BEFORE you render: 1. WHO IS IN FRAME — never assume or silently substitute a stranger. A framework with a person and no face photo is refused (nothing charged): ask the user once — themselves (a face photo, identity-locked), a generated person (`castGenericPerson:true`), or a people-free framework. 2. TEXT — default is a CLEAN render with the headline TYPESET over it (free, legible, correctly spelled): pass `headline`. `bakeText:true` only on an explicit ask for words painted INTO the image. Never infer text intent from the topic. 3. HOW MANY — ask once: one, or a SET (offer 4: one concept at different emotions / camera takes). Default 1; `variants` caps at 16. `emotion` is the biggest CTR lever on a face (identity lock is automatic for every face photo). To fix a finished one, re-call with `tweak` + `sourceImage` for a surgical edit (emotion / background / background_color / rim_light) — tweaks chain. ALWAYS check the returned postRenderCheck against the image before presenting it. PROMPT LANGUAGE — write every DESCRIPTIVE field (`sceneBrief`, `keyElements`, `location`, `composition`, `background`, `topic`, each person's `describe`, every `reference`) in ENGLISH, translating the user's words: the models render English better. `headline`, `headlineLines` and `bakedUiText` stay verbatim in the user's language.
Paramètres, retour & exemple (33)
Paramètresfont· string · optionnel
headline font: Anton (default) or any Google Fonts familylogo· string · optionnel
a brand logo URL or path to place into the compositionsplit· object · optionnel
split/panel LAYOUT — only when the user asks for one ("split", "before/after", "versus screen"). "X vs Y" as a SCENE stays one unified frametakes· number · optionnel
camera takes per emotion, 1–4: designed framing / low-angle hero / extreme close-up / wide dutch tilttopic· string · optionnel
the video's topic — used to pick the hero object when you don't name keyElementstweak· object · optionnel
surgical pixel-faithful edit of a FINISHED thumbnail (needs sourceImage): kind emotion / background / background_color / rim_light, or any other kind with the edit in words as valuelogo3d· boolean · optionnel
first turn the flat logo into a volumetric 3D render (one extra billed image), then composite thatpeople· array · optionnel
people described in prose instead of by photo (each still gets the chosen expression)emotion· string · optionnel
the expression on the face (default 'shock') — shock · hype · fear · confusion · determination · smug · charisma · disgust · awe · rage · laugh, or your own phrasebakeText· boolean · optionnel
default false. true paints the headline INTO the generation — only on an explicit user ask; it leaks garbled text elsewhere in the frameemotions· array · optionnel
render one variant per emotion (variants = emotions × takes, max 16)headline· string · optionnel
2–4 word headline. Typeset OVER the finished render by default (free, always legible); newlines split it into stacked lineslocation· string · optionnel
place, time of day, weather, atmosphererimColor· string · optionnel
colored back+hair light — ONLY when the user names one: 'ice-blue' / 'neon-magenta' / 'toxic-lime' / 'amber-gold' / 'pure-white'variants· number · optionnel
how many thumbnails to render (default 1, max 16). Each is its own billed render — offer a set of 4 rather than assumingframework· string · optionnel
concept framework id (default 'posed_portrait') — before_after · social_ui · three_step · screenshot · posed_portrait · posed_action · specific_day · graphical · landscape · map_aerial · product · adding_text · repetition · size_difference · news_clip · amplified_reality — or your own concept in wordsreference· object · optionnel
fields YOU extracted by eye from a reference thumbnail. Extract ALL of: brief (one dense sentence on the concept), subject (pose/action generically, NEVER a specific identity), elements, location, composition, background, split (boolean), split_count, person_count (0-3), emotion (one of the 11 presets or 'other'), emotion_detail (one vivid sentence covering eyes, brows, mouth, head angle). emotion + emotion_detail carry the reference's actual facial performance, which is the single biggest CTR lever on a face; split/split_count reproduce its panel structure. The reference image itself is never sent to the modelbackground· string · optionnel
override the default bold saturated colour-field backgroundfaceImages· array · optionnel
up to 3 face photos (URLs or local paths) — each becomes a locked CHARACTER identity, in ordersceneBrief· string · optionnel
what the thumbnail depicts — the concept in one dense sentence, rendered exactlyaspectRatio· string · optionnel
'16:9' (YouTube, default) / '9:16' (Shorts) / '4:5' (Instagram) / '4:3' / '1:1'bakedUiText· string · optionnel
short label for a text-carrying framework (a chat bubble, a DAY N badge, a news lower-third, a map callout) — needs frameworkRequested:truecomposition· string · optionnel
override the default large-foreground-subject compositionkeyElements· string · optionnel
signature props / effects that make it pop — oversized, flying toward camerasourceImage· string · optionnel
the finished thumbnail URL a `tweak` edits; tweaks chain, so feed each accepted output into the nextoverlayStyle· string · optionnel
headline style: beast (default), fire, neon-lime, clean-glass, marker, or your own CSS declarationsforceGenerate· boolean · optionnel
render the 'screenshot' framework anyway (it is normally a real video frame, not a generation)headlineLines· array · optionnel
explicit headline lines (up to 3) — overrides splitting `headline` on newlinesheadlinePlace· string · optionnel
bottom (default), top, center, or a 0-1 fraction from the top; never over the facelogoPlacement· string · optionnel · auto | overlay | in_scene
overlay (exact, flat) | in_scene (from the file, checked); auto: in_scene with logo3drestrainedGrade· boolean · optionnel
true for a calm / premium / muted look instead of the default punchy poster gradecastGenericPerson· boolean · optionnel
pass true only after the user has explicitly chosen a generated stranger over their own faceframeworkRequested· boolean · optionnel
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
Exemple d'appel{"name": "make_thumbnail", "arguments": {"font": "texte", "logo": "texte", "split": {}, "takes": 1, "topic": "texte", "tweak": {}}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - manage_sheet_tabs≈ 393 tokens
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 count, then confirm:true + confirmCells. Google does not allow removing the LAST remaining tab in a file, and that is refused by name with the way out (clear it, or delete the whole file with delete_drive_file). Every action is read back from the spreadsheet before it is reported as done.
Paramètres, retour & exemple (8)
Paramètrestab· string · optionnel
which tab — its title or numeric sheetId (rename / delete)title· string · optionnel
the name for the new tab (action:"add")action· string · requis · add | rename | deleteconfirm· boolean · optionnelnewTitle· string · optionnel
what to rename the tab to (action:"rename")sheetUrl· string · optionnelconfirmCells· number · optionnelspreadsheetId· string · optionnel
Exemple d'appel{"name": "manage_sheet_tabs", "arguments": {"action": "add"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - mine_angles≈ 634 tokens
Mine ad ANGLES from real customer language: gathers the customer's own words (Reddit, TikTok, the brand's review page + review-site results) and returns a RANKED angle bank — each angle tagged (pain / outcome / identity / fear / competitive-displacement / social-proof / contrast), 2-5 VERBATIM proof quotes, a 0-100 score with breakdown, and a ready-to-run hook in the customer's own voice. Reads YOUR saved brand (pass brandId to target a specific profile — that switches this key's active profile like use_brand). To tear down a COMPETITOR use competitor_teardown instead. YOUR OWN REVIEWS: pass `reviews` (a list of review texts, or one pasted block: one per line, numbered, blank-line separated, or a CSV with a review column) and/or `reviewsUrl` (a CSV, TXT or JSON file from upload_file, or a review page; on a local CLI a file path works too). They are first-class evidence: every quote from them is checked word for word against what you sent and labelled 'your reviews', and a quote that is not verbatim is dropped and counted. useOwnReviewsOnly:true mines only your reviews and gathers nothing public. Limits: 300 reviews, 2,000 characters each, 40,000 in total; over that it is refused at no cost, so send fewer or split into batches. Each angle comes back with `next`: the exact plan_variations and generate_image arguments that turn it into finished statics (one generate_image per angle = statics with distinct angles). Spends a few credits.
Paramètres, retour & exemple (4)
ParamètresbrandId· string · optionnel
a profile id/name from list_brands to mine for; omit to use the active profilereviews· ? · optionnel
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 · optionnel
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 · optionnel
true = mine only your reviews, no public search (no search credits). Default false = merge with public customer language.
Exemple d'appel{"name": "mine_angles", "arguments": {"brandId": "texte", "reviews": "texte", "reviewsUrl": "texte", "useOwnReviewsOnly": true}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - multiply_ad≈ 730 tokens
MULTIPLY a winning video ad into N variants: each gets a NEW character, outfit, location and/or objects while the cut, the camera motion, the pacing and the ORIGINAL AUDIO stay exactly as they were (that is what made the ad work), and any burned-in captions are removed. Pass the source video URL (a previous render, a job result, list_library, or the top performer from post_performance / meta_insights). HOW: the source video itself DRIVES each variant (motion transfer from one image-edited opening frame), so every variant comes back the SAME LENGTH as the source with the same cut, the same performance and the original audio — only the person, outfit, set and props change. Sources up to 30 seconds work as they are; longer ones are refused for free with the way out (trim it first: post_edit with ops [{op:'trim', start:0, end:30}] — clip_video is the AI highlight clipper, not a trim). Returns the plan and ONE JOB PER VARIANT — call get_job on each until it reports done; do not describe a variant before its URL arrives. Cost is quoted per variant in the reply (use dryRun:true to see the plan and the quote without rendering). Regions: pass regions:['Berlin','Tokyo'] to restyle variants per market; translation is a separate, explicit step — dub_video on a finished variant. `change` is what should change, in the user's own words ('older women', 'winter streets', 'swap the mug for our bottle'): every variant applies it and the variants still differ around it; a part of it about the voice, words, language, music, length or captions cannot change here, and the reply says so and names the tool that can.
Paramètres, retour & exemple (7)
Paramètresaxes· array · optionnel
what to vary: character, outfit, location, objects (default all four), or your own, e.g. "season"count· number · optionnel
how many variants, 1-12 (default 6)notes· string · optionnel
anything the variants must respect, e.g. "keep it women 25-40", "no gyms"video· string · requis
the source video URLchange· string · optionnel
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)dryRun· boolean · optionnel
true = return the plan and the quote, render nothingregions· array · optionnel
markets to restyle for, one or more variants each, e.g. ["Berlin","Tokyo","São Paulo"] — visuals only; audio is never translated here
Exemple d'appel{"name": "multiply_ad", "arguments": {"video": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - plan_ad≈ 1688 tokens
Creative director: turn a brand + product/brief into a finished ad CONCEPT — copy variants (headline/primary/cta) plus an image_concept.prompt OR a video_storyboard, with the resolved recipe + the model ids to render with. Renders nothing; chain its output into generate_image / generate_video. THE USER’S EXPLICIT LENGTH IS SOVEREIGN: when they name a duration ("a 30 second ad", "make it 45s"), pass it as durationSeconds — the board is then AUTHORED to that length (its scenes sum to it) and render_ad renders it as one clip or stitched acts accordingly. Leaving it out lets the planner pick its own default, which is how an explicit ask silently becomes a 15s spot. The user’s OWN screenshots or pictures cut into the video full-screen go in `inserts`: the board keeps those seconds free of any screen, and render_ad lays the real pictures in. Spends credits.
Paramètres, retour & exemple (12)
Paramètreshook· string · optionnel
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 delivered in this brief is DROPPED with the reason rather than rendered wrongly — an on-screen-text hook on an authentic/UGC ad is the one that bites, because that register carries zero on-screen text.brand· ? · optionnel
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 onedraft· object · optionnel
ONLY after a video refusal that offered a light draft: the {model, durationSeconds} it named. The plan is then authored to that length and priced on that model. Never invent one — a video the account cannot cover is refused BEFORE planning with the three options (image / add credits / this draft when one fits), and the user chooses.format· string · optionnel · auto | image | video
'image', 'video', or 'auto' when unspecifiedrecipe· string · optionnel
a recipe id from hermoso_capabilities to force an archetypetalent· string · optionnel · auto | creator | product_only
who is on camera; omit to follow the format. Say 'someone new' in product to skip reusing a saved creator.inserts· array · optionnel
VIDEO ONLY: the user's own SCREENS (an app, a page, a dashboard, a document, a review, a chart) shown at these seconds as a rounded card over the upper middle of the footage, which keeps playing around it (look 'full' covers the frame, only when the user asked), the voice running underneath. Fewer and shorter: about one per 4 s of film, 1.2-2 s each, on the line that talks about it; never in the first second or the last 1.5 s. The plan frames those shots for the card, keeps them free of screens, and returns the pictures as creative.inserts for render_ad to lay in (about 2 credits). A photo of a person, product or place (kind 'photo') is not laid in: pass a product photo as the brand's product photo and a person as a saved creator. One that does not fit is left out and the reply says so.product· string · requis
what to advertise + any angle/offer the user specifiedsetting· string · optionnel
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 setting.language· string · optionnel
output language for the ad copy (e.g. Spanish) — default Englishreference· string · optionnel
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 video, X post, YouTube Short/video or a direct video file — which is WATCHED first (frames + voiceover/on-screen-text transcript) so the concept keeps its hook, structure and pacing. To remake one video for this brand at its own length, clone_video is the direct tooldurationSeconds· number · optionnel
VIDEO ONLY — the total spot length the user explicitly asked for, in seconds, copied verbatim (30 for "a 30 second ad"). The planner authors the storyboard TO it: the scenes’ seconds sum to it and the script is word-budgeted for it. Supported range 4–180; anything outside is CLAMPED to it (the reply says so). A length that fits ONE clip of the render model renders as a single continuous pass; anything longer is STITCHED from acts filled to that model’s clip maximum with the remainder last (on a 15s-clip model, 40 -> 15+15+10 and 17 -> 13+4) — never time-compressed. The maximum is the model’s own: 15s on most, 30s on the longest-clip model, so a 30s ad can be one unbroken take rather than two acts. Omit when the user named no length; do NOT pass a guess: when it is omitted the planner picks a length suited to the format, and a length the user names always wins.
Exemple d'appel{"name": "plan_ad", "arguments": {"product": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - plan_variations≈ 302 tokens
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.
Paramètres, retour & exemple (4)
Paramètresbrand· ? · optionnel
brand name or profile object; OMIT to use the workspace’s saved brandcount· integer · optionnel
how many distinct variants (default 6)product· string · requis
what to advertiselanguage· string · optionnel
output language for the variant copy (e.g. Spanish) — default English
Exemple d'appel{"name": "plan_variations", "arguments": {"product": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - post_edit≈ 1702 tokens
MECHANICAL post-production on an EXISTING video (URL): ordered primitives run by ffmpeg in seconds, ~2 credits flat, NO AI model, as a NEW video. Ops: a branded end card (adds its seconds), trim, speed (0.5-2x), mute (whole or a window), audio_gain (-20..+6 dB), fade_out, music (a bed UNDER the clip, its own sound kept and ducked under: omit track = a free library track picked by mood, no attribution; or track = an audio link: an upload_file URL, a find_sound link, a video post's sound; track 'generate' composes it, paid, ONLY when the user asks; start/end, db, replace), voiceover (text spoken OVER it, its sound ducked; ~2.5 words/s; voice female|male; +~1 credit), watermark (brand logo), grain (anti-AI), text (timed words in a native look: style 'tiktok-classic' default / 'clean-minimal' / 'note-style' or a textStyle; start/end), join (this video FOLLOWED BY clips[]: Library URLs, direct files or public post links, as one 1080x1920 video, loudness matched). Presets are shortcuts: text/watermark take any x/y, grain any amount, join any ffmpeg transition, a bridge any sound link. 'A viral hook, then our clip' = videoUrl: the hook's post link + [{op:'join', clips:[{url: ours}], bridge}], and it ALWAYS gets a bridge unless the user asks for a bare cut: {kind:'impact'} cuts the hook just before its payoff (found from the footage; cutAt overrides) and lands our clip on a punch-in and flash, with the payoff sound FROM THE HOOK ITSELF: its own audio carries across the cut, else one generated from its frames (up to 8 credits), else a neutral impact. {kind:'text', text, then?} only when the user asks for words over the cut. No voiceover bridge: have our clip's host say the connecting line. matchCut = where our clip starts. ANY OTHER EDIT, op or bridge (whip, zoom, freeze, wipe, split screen, generated shot) is edit_timeline: free keyframes, any size. NEVER generate_video/render_ad for these.
Paramètres, retour & exemple (6)
Paramètresops· array · requis
the ordered edit plan (max 6 ops)accent· string · optionnel
override the brand accent hexdomain· string · optionnel
override the brand websitedryRun· boolean · optionnel
true = return the estimated credits this edit reserves and run nothingvideoUrl· string · requis
the video to edit: a render / Library URL, a direct file, or a public post linkbrandName· string · optionnel
override the workspace brand name
Exemple d'appel{"name": "post_edit", "arguments": {"ops": [], "videoUrl": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - post_performance≈ 543 tokens
Aggregate this brand's published posts to answer WHICH HOOKS AND SUBJECTS WORK. Groups by hook (default), subject, format (recipe), channel, media format or posting hour, reports the engagement RATE within each channel, and ranks the best and worst POSTS in each channel. Describe a post by the creative it carried (what it shows, its format, its link), not by its caption — the caption is the least important part of a post. THREE THINGS IT DELIBERATELY WILL NOT DO, and you should repeat them rather than paper over them: (1) it never sums metrics across channels — a LinkedIn impression and a TikTok view are different units, so every comparison is within one channel; (2) it SUPPRESSES a verdict below 5 measured posts and says so, because a confident recommendation from 3 posts is worse than none; (3) a post with no recorded hook (published outside Hermoso, or backfilled without a creation match) counts toward channel and format totals but never votes on which hook works. Present the `finding` verbatim if there is one, and the reason if there is not. Read-only, 0 credits.
Paramètres, retour & exemple (4)
Paramètresaxis· string · optionnel · hook | subject | recipe | channel | media | hour
what to group by — default hook; recipe = the format of the creativedays· number · optionnel
look back N days (1-730) over the whole history; omit for the recent posts onlybrand· string · optionnel
WHICH PROFILE the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a profile this connection is not pinned to; applies to THIS CALL ONLY. A name that matches no profile, or two, is REFUSED and nothing is done.channel· string · optionnel
restrict to one channel
Exemple d'appel{"name": "post_performance", "arguments": {"axis": "hook", "days": 1, "brand": "texte", "channel": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - post_to_bluesky≈ 1458 tokens
Use this when the user wants to post to Bluesky. Publishing costs no credits. Publish a post to Bluesky as the connected account. Text up to 300 characters — Bluesky ALSO caps a post at 3000 UTF-8 bytes, so an emoji-heavy post can be under 300 characters and still be refused; Hermoso checks both before spending the round trip and says which limit and by how much. MEDIA: either up to 4 images (imageUrls + altText) OR one MP4 video (videoUrl + videoAlt), never both — a Bluesky post record carries a single embed and images and video are two different embed types. Video is MP4 only, up to 300MB at Bluesky's end (Hermoso can fetch up to 150MB from a URL), with optional WebVTT caption tracks; the aspect ratio is measured from the file. Bluesky requires a CONFIRMED EMAIL on the account before it will process any video — if it is unconfirmed you get a refusal saying so, and reconnecting will not help. Links in the text are made clickable automatically. LINK CARDS: Bluesky does NOT scrape links, so a URL posted bare renders as plain blue text — the client composing the post has to build the card. Hermoso builds one AUTOMATICALLY when the post has a URL and NO media: it fetches the page, uses its title/description and uploads its image as the card thumbnail. Pass linkCard:false to suppress it, or linkCard:{uri,title,description,thumbUrl} to control it (give both title and description and the page is not fetched at all). A POST CARRIES ONE EMBED, so a card and images/video cannot both ride: if you pass linkCard explicitly ALONGSIDE media the call is REFUSED by name rather than silently dropping one, and if the URL was merely in the text the MEDIA WINS and the reply says the card was skipped (the link stays clickable either way). Returns the post's public bsky.app URL. Connect at Settings > Connectors > Bluesky.
Paramètres, retour & exemple (17)
Paramètreshook· string · optionnel
the post's angle — a list_hooks id or your own wording, reused exactlytext· string · requis
The post, up to 300 characters / 3000 UTF-8 bytes.brand· string · optionnel
WHICH PROFILE this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no profile, or two, is REFUSED and nothing is posted.langs· array · optionnel
BCP-47 language tags, e.g. ['en'].ideaId· string · optionnel
short id of the content-plan idea this post came fromrecipe· string · optionnel
the post's FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per formataccount· string · optionnel
WHICH connected account of this channel to post as — its @handle or id from list_connector_accounts. Needed only when the profile has more than one bluesky account connected (several and none named is refused by name, never guessed); omit when there is one.altText· ? · optionnel
Alt text — an ARRAY, one per image in the same order, or a single STRING to describe every image with it. WRITE ONE: Bluesky’s own lexicon makes `alt` a REQUIRED property of every image, so a post without it is undescribed by design rather than by omission, and Bluesky users expect it. No maximum length is published, so nothing is truncated.subject· string · optionnel
what the post is aboutcaptions· array · optionnel
Up to 20 WebVTT caption tracks: [{lang:'en', url:'https://…/en.vtt'}] or [{lang:'en', content:'WEBVTT\n\n00:00…'}]. Each file is capped at 20000 bytes.linkCard· ? · optionnel
Rich link card (`app.bsky.embed.external`). OMIT for the default (a card is built automatically when the post has a URL and no media). `false` never builds one. `true` builds one from the first URL in the text. An object {uri,title,description,thumbUrl} overrides any field — supply BOTH title and description and the page is never fetched. Cannot be combined with imageUrls/videoUrl: a post has ONE embed, so an explicit linkCard beside media is refused.videoAlt· string · optionnel
Alt text describing the video, for accessibility.videoUrl· string · optionnel
One public MP4 URL. Cannot be combined with imageUrls. Bluesky transcodes it, which takes a minute or two.imageUrls· array · optionnel
Up to 4 public image URLs to attach. Cannot be combined with videoUrl.platformCover· boolean · optionnel
VIDEO COVER. Bluesky has no cover setting and shows the video’s FIRST frame, so by default Hermoso checks it and, only when that frame is blank (a template ad’s empty opening card), sends Bluesky a copy with the first frame replaced by the video’s best frame. Your Library file is never changed. true = send the file exactly as it is.allowDuplicate· boolean · optionnel
post it even though an identical post was just madeidempotencyKey· string · optionnel
any stable string: a repeat within 24h returns the original post instead of posting again
Exemple d'appel{"name": "post_to_bluesky", "arguments": {"text": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - post_to_google_business≈ 912 tokens
Publish a Post to the brand’s Google Business Profile — the panel that appears on Google Search and Maps for the business. Text, optionally ONE PHOTO, and a call-to-action button. Google’s Posts API accepts NO VIDEO, so pass a still image. This PUBLISHES immediately and publicly on the business listing — show the user the exact text, photo and button and get an explicit yes BEFORE calling. If the account manages several listings, call list_business_locations first and pass locationId. EVENT and OFFER posts both REQUIRE a title and a start date (Google’s rule). On an OFFER, Google IGNORES the button’s link — pass redeemOnlineUrl instead. A CALL button dials the number on the listing and takes no link. Needs Google Business Profile connected (Settings > Connectors > Google Business Profile).
Paramètres, retour & exemple (18)
Paramètreshook· string · optionnel
WHAT ANGLE this post is built on, recorded only at publish time. post_performance ranks hooks on it (a winner needs 5 posts sharing ONE hook), so pass a list_hooks id (e.g. "direct_callout", "before_after") or reuse your own wording EXACTLY across a campaign. Omit it and this post never votes on which hook works.link· string · optionnel
the URL the button opens — not for CALL, and ignored on an OFFERbrand· string · optionnel
WHICH PROFILE this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no profile, or two, is REFUSED and nothing is posted.title· string · optionnel
headline — REQUIRED for EVENT and OFFERideaId· string · optionnel
short id of the content-plan idea this post came fromrecipe· string · optionnel
the post's FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per formatendDate· string · optionnel
YYYY-MM-DD, defaults to startDatesubject· string · optionnel
WHAT THIS POST IS ABOUT — product, feature, offer or theme (e.g. "winter coat", "free trial"). post_performance's second grouping axis: reuse the exact wording, as with hook.summary· string · optionnel
the body text of the PostimageUrl· string · optionnel
a Hermoso render image URL (or an upload_file url) to show on the PoststartDate· string · optionnel
YYYY-MM-DD — REQUIRED for EVENT and OFFERtopicType· string · optionnel · STANDARD | EVENT | OFFER | ALERT
default STANDARDactionType· string · optionnel · BOOK | ORDER | SHOP | LEARN_MORE | SIGN_UP | CALL
the button on the PostcouponCode· string · optionnel
OFFER onlylocationId· string · optionnel
which listing, e.g. 'locations/123' from list_business_locations — only needed when the account manages more than onelanguageCode· string · optionnel
BCP-47 language of the Post, default 'en'redeemOnlineUrl· string · optionnel
OFFER only — this is the link Google actually uses on an offertermsConditions· string · optionnel
OFFER only
Exemple d'appel{"name": "post_to_google_business", "arguments": {"hook": "texte", "link": "texte", "brand": "texte", "title": "texte", "ideaId": "texte", "recipe": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - post_to_linkedin≈ 872 tokens
Use this when the user wants to post to their personal LinkedIn profile. Publishing costs no credits. Publish a post to the user’s connected LinkedIn profile — text, and optionally an image (pass its served URL as imageUrl). The image does NOT have to be something Hermoso generated: LinkedIn is served the bytes from us, so the URL must be Hermoso-hosted, and upload_file turns ANY file the user already has into exactly that. This PUBLISHES immediately and PUBLICLY — ALWAYS show the user the exact text and get an explicit yes BEFORE calling. Needs a connected LinkedIn account (Settings > Connectors > LinkedIn).
Paramètres, retour & exemple (11)
Paramètreshook· string · optionnel
WHAT ANGLE this post is built on, recorded only at publish time. post_performance ranks hooks on it (a winner needs 5 posts sharing ONE hook), so pass a list_hooks id (e.g. "direct_callout", "before_after") or reuse your own wording EXACTLY across a campaign. Omit it and this post never votes on which hook works.text· string · requis
the post textbrand· string · optionnel
WHICH PROFILE this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no profile, or two, is REFUSED and nothing is posted.ideaId· string · optionnel
short id of the content-plan idea this post came fromrecipe· string · optionnel
the post's FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per formatsubject· string · optionnel
WHAT THIS POST IS ABOUT — product, feature, offer or theme (e.g. "winter coat", "free trial"). post_performance's second grouping axis: reuse the exact wording, as with hook.imageUrl· string · optionnel
a Hermoso-hosted image URL to attach (≤12MB) — a Hermoso render, or ANY file of the user’s own put through upload_file first. An arbitrary external host is refused (we fetch the bytes ourselves).imageUrls· array · optionnel
A CAROUSEL IS NOT AVAILABLE ON A PERSONAL PROFILE — LinkedIn's organic multi-image post publishes from a COMPANY PAGE. Passing several here is refused by name rather than posting slide 1; use post_to_linkedin_page instead.visibility· string · optionnel · PUBLIC | CONNECTIONS
default PUBLICallowDuplicate· boolean · optionnel
post it even though an identical post was just made or attempted. Only pass this when the user genuinely wants the same thing posted twice, or when you have LOOKED at the account and confirmed a timed-out attempt did not land.idempotencyKey· string · optionnel
SAFE RETRIES — any stable string: a repeat of the SAME publish within 24h returns the ORIGINAL post id instead of posting again (an identical publish is also auto-recognised for 10 minutes). On a timeout or an ambiguous error, CALL AGAIN WITH THE SAME KEY: it reports the original post or publishes it once. It never posts twice.
Exemple d'appel{"name": "post_to_linkedin", "arguments": {"text": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]} - post_to_linkedin_page≈ 2509 tokens
Use this when the user wants to post to a LinkedIn company Page. Publishing costs no credits. Publish a post to one of the user’s LinkedIn COMPANY PAGES — text, plus optionally an image, a video, a 2–20 image CAROUSEL (LinkedIn calls it a MultiImage post; pass the slides in order as imageUrls[]), or a LINK POST with a real preview card (linkUrl). USE linkUrl WHENEVER THE POINT OF THE POST IS A LINK: LinkedIn disables URL scraping for API partners, so a url sitting in the text renders as plain text with no card, and the card’s title, description and image only exist if you pass linkTitle / linkDescription / linkThumbnailUrl — read them off the page and supply them. The media need not be a Hermoso render — it must be Hermoso-HOSTED because we upload the bytes to LinkedIn ourselves, and upload_file turns ANY file the user already has into such a URL. ORGANIC CAROUSELS ARE COMPANY-PAGE ONLY — a personal profile cannot publish one and is refused by name, so send a deck here rather than to post_to_linkedin. This is a DIFFERENT thing from post_to_linkedin, which publishes to the person’s own profile: pick the one the user actually asked for and never substitute. organizationId comes from list_linkedin_pages; omit it only when the account administers exactly one Page. This PUBLISHES immediately and PUBLICLY — ALWAYS show the user the exact text and get an explicit yes BEFORE calling. A VIDEO POST CAN CARRY CAPTIONS AND ITS OWN COVER, and both are attached only during the upload: pass captionsSrt (SubRip content — LinkedIn is watched with the sound off) and videoThumbnailUrl (otherwise LinkedIn picks a frame for you). LinkedIn does NOT allow the image, video, captions or thumbnail of a published post to be swapped afterwards, so get all of that right first (the copy can still be edited with manage_linkedin_post).
Paramètres, retour & exemple (24)
Paramètreshook· string · optionnel
WHAT ANGLE this post is built on, recorded only at publish time. post_performance ranks hooks on it (a winner needs 5 posts sharing ONE hook), so pass a list_hooks id (e.g. "direct_callout", "before_after") or reuse your own wording EXACTLY across a campaign. Omit it and this post never votes on which hook works.text· string · requis
the post textbrand· string · optionnel
WHICH PROFILE this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no profile, or two, is REFUSED and nothing is posted.title· string · optionnel
video titleideaId· string · optionnel
short id of the content-plan idea this post came fromrecipe· string · optionnel
the post's FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per formataltText· ? · optionnel
accessibility alt text (max 4086 characters, ~120 recommended). A STRING describes every image; an ARRAY describes each slide of a multi-image post separately, in slide order — LinkedIn stores altText per image, and their own sample request carries a different one on each. Not available on a PERSONAL-profile post: LinkedIn’s member posting API has no alt-text field at all.linkUrl· string · optionnel
publish a LINK POST — LinkedIn renders a real preview card for this URL instead of leaving a bare link in the text. Mutually exclusive with imageUrl / videoUrl / imageUrls: LinkedIn’s content field is a union, so combining them is refused by name rather than one being dropped.subject· string · optionnel
WHAT THIS POST IS ABOUT — product, feature, offer or theme (e.g. "winter coat", "free trial"). post_performance's second grouping axis: reuse the exact wording, as with hook.imageUrl· string · optionnel
a Hermoso-hosted image URL — a render (list_library), or ANY image of the user’s own passed through upload_file first. An arbitrary external host is refused.videoUrl· string · optionnel
a Hermoso-hosted video URL — a render, or the user’s own footage via upload_file. LinkedIn processes it before publishing, which takes a minute.coverAtMs· number · optionnel
THE VIDEO COVER of a videoUrl post, as ONE frame: milliseconds from the start (7000 = the frame at 7s). That frame is uploaded as LinkedIn’s video thumbnail (only possible while the video uploads). videoThumbnailUrl wins.imageUrls· array · optionnel
CAROUSEL — an ORDERED list of image (and, where the channel allows, video) URLs published as ONE post the viewer swipes through. THIS IS NOT “post several” — it is a single post with several slides, which is what a multi-slide creative (a listicle, a “1/6 · SWIPE” deck) actually needs; publishing only its first slide tells the viewer to swipe at something that cannot. The ORDER is the product. Limits per channel: Instagram 2–10 (images, videos or a mix), Threads 2–20 (mix allowed), Facebook 2+ (Meta publishes no documented maximum; Hermoso caps the upload fan-out at 30 and says so), LinkedIn company Pages 2–20 (images only), Pinterest 2–5 (images only), TikTok up to 35. One url here is simply an ordinary single post. Anything a channel cannot do is REFUSED with the real reason — nothing is ever quietly downgraded to one slide.linkTitle· string · optionnel
the headline ON the preview card. LINKEDIN NEVER SCRAPES THE PAGE — their Posts API disables URL scraping for API partners outright — so if you do not pass this the card renders UNLABELLED. Fetch the page’s own title and pass it.visibility· string · optionnel · PUBLIC | CONNECTIONS
default PUBLICcaptionsSrt· string · optionnel
CLOSED CAPTIONS for a videoUrl post — the SubRip (.srt) CONTENT itself, cue numbers and `00:00:00,000 --> 00:00:02,000` timing lines included, NOT a URL and NOT the plain script (a file with no timings is refused, because LinkedIn would accept it and then silently never show it). Most of LinkedIn is watched with the sound off, so an uncaptioned video is one most of the feed never hears. LinkedIn allows ONE caption file per video and ENGLISH ONLY; it can be attached only WHILE the video is uploaded, never added to a published post; and it is processed asynchronously, so the reply confirms it was UPLOADED and never that it is visible yet. Requires videoUrl — passing it on an image, carousel or link post is refused by name.platformCover· boolean · optionnel
VIDEO COVER. Omit it (the default) and Hermoso sets the video’s best frame — the same frame as its Library thumbnail — as the cover (LinkedIn’s video thumbnail upload). true = send no cover and let the platform pick (usually the first frame). A cover you pass yourself always wins.allowDuplicate· boolean · optionnel
post it even though an identical post was just made or attempted. Only pass this when the user genuinely wants the same thing posted twice, or when you have LOOKED at the account and confirmed a timed-out attempt did not land.idempotencyKey· string · optionnel
SAFE RETRIES — any stable string: a repeat of the SAME publish within 24h returns the ORIGINAL post id instead of posting again (an identical publish is also auto-recognised for 10 minutes). On a timeout or an ambiguous error, CALL AGAIN WITH THE SAME KEY: it reports the original post or publishes it once. It never posts twice.organizationId· string · optionnel
numeric Page id from list_linkedin_pagestargetAudience· object · optionnel
LINKEDIN COMPANY PAGE POST ONLY — show the post only to Page followers matching these facets (URNs or bare numeric ids; search_linkedin_ads_targeting finds them). LinkedIn requires the matching audience to be over 300 followers and refuses a smaller one. Personal-profile posts cannot be targeted.linkDescription· string · optionnel
the sub-line on the preview card. Same rule as linkTitle: absent means blank, because LinkedIn will not fetch it.linkThumbnailUrl· string · optionnel
a Hermoso-hosted image used as the card’s picture (uploaded to LinkedIn for you). Without it the card has no image.videoThumbnailUrl· string · optionnel
the COVER IMAGE for a videoUrl post — a Hermoso-hosted image (a render, or any picture of the user’s via upload_file). Without it LinkedIn adds a system-generated thumbnail, which on an ad is usually whatever the first frame happens to be. Like captions this can only be set WHILE the video is uploaded, never afterwards. Requires videoUrl. This is NOT linkThumbnailUrl, which is the picture on a link-preview card.
Exemple d'appel{"name": "post_to_linkedin_page", "arguments": {"text": "texte"}}Exemple de retour{"content": [{"type": "text", "text": "…"}]}
+ 65 autres outils (affichage limité à 120).
C'est votre serveur ?
Revendiquez-le sur l'espace créateur (preuve GitHub, domaine ou npm) pour afficher le badge « propriétaire vérifié » et lancer un pentest comportemental à la demande afin d'obtenir le badge « Vérifié ».