Hermoso

io.github.hermoso-ai由 GitHub 帳號 hermoso-ai 發布v0.1.529更新於 Nov 8, 2026

AI marketing agent and AI ad generator: 1182 tools, usable alone. No model markup on Starter and up.

已驗證Streamable HTTP可網頁執行溝通與協作商業與電商媒體與設計

概覽

AI 產生的概覽

Hermoso 是一個 AI 行銷與廣告代理,讓助理研究競品廣告、生成圖片與影片廣告、發佈貼文並管理廣告投放。

可以做什麼
  • 廣告研究:搜尋 Meta、Google、LinkedIn 廣告庫以及 TikTok、Instagram、YouTube、Reddit、Threads 的自然內容,拆解競品並挖掘創意角度
  • 創意生成:生成圖片、影片、UGC 虛擬人物與配音廣告,規劃與算圖廣告,批次產生靜態廣告,剪輯與重新取景影片
  • 廣告投放管理:在 Meta、Google、TikTok 等廣告平台上建立、讀取與管理廣告活動、廣告組合、廣告、受眾、預算與洞察數據
  • 發佈與排程:發佈到十五個社群頻道,並讀取貼文成效、留言與私訊
  • 帳戶與計費:查詢功能與點數價格、讀取帳單狀態、購買點數或升級方案
適用情境
適合讓助理從頭到尾處理行銷工作:研究競品正在投放的廣告、製作廣告素材、發佈或排程到社群頻道,或建立並回報付費廣告投放。各項功能彼此獨立,可以只使用其中一項。
執行需求
可使用託管的遠端端點並以 OAuth 登入,或使用以 npx 執行的本機 npm 套件。連線託管服務需要 Hermoso 帳戶與代理金鑰(HERMOSO_TOKEN,即 hmk_ 金鑰);HERMOSO_API_BASE 可指向其他 API 來源。透過 CLI 登入會儲存憑證。連接社群或廣告帳戶需要瀏覽器完成 OAuth 授權。
安裝前請注意
算圖與 Ad Spy 研究會消耗付費點數,方案為付費訂閱;伺服器也能購買點數、升級方案,並說明了由代理自行註冊與付款的流程。它能在已連接的帳戶上發佈、排程、編輯與刪除貼文,建立或修改廣告投放,發佈到 X 會依次數扣點。它持有 bearer 代理金鑰,並連接許多第三方廣告與社群平台。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 Hermoso,將其新增到工作區。
  2. 連接 所需的 憑證。它們會被私密儲存,絕不會在公開頁面上展示。
  3. 為需要使用其工具的對話啟用該服務。

Web executable,透過 Streamable HTTP。 遠端服務在工作區中設定後即可從網頁執行環境執行。

交給代理程式安裝

把這段提示詞貼上到 Claude Code、Codex、Cursor 等代理程式,或 SourceWeft 對話中。代理程式會讀取該伺服器的安裝指南,先向你展示發布者、端點和登入方式,經你同意後再把它新增到你的客戶端。金鑰不會寫進任何命令。

閱讀 https://sourceweft.com/mcp/io.github.hermoso-ai%2Fhermoso/install.md,並按其中說明把這個 MCP 伺服器接入我的客戶端。修改任何設定之前,先告訴我它的發布者、端點和登入方式,等我確認。不要把 API 金鑰寫進命令或我會提交的檔案;問我金鑰應從哪裡取得。

檢視代理程式所依據的安裝指南

其他 MCP 客戶端

把它新增到你客戶端的 mcpServers 設定中。 客戶端會在首次連接時完成 OAuth 登入。

{
  "mcpServers": {
    "hermoso": {
      "type": "http",
      "url": "https://app.hermoso.ai/mcp?src=registry"
    }
  }
}

README

Hermoso: AI marketing agent and AI ad generator for Claude, ChatGPT and Cursor (MCP server, CLI and skills)

The AI marketing agent with an AI ad generator built in, run from any AI agent: Claude Code, Claude.ai, Cursor, Codex, or your own scripts. Research the ads already winning in a market, generate image & video ads (your real product composited in, copy + CTA included), publish them to your own social channels, and build & manage the ad campaigns behind them, all over MCP tools, a CLI, or installable Claude skills.

What it does

  • Meta Ads MCP server. Build, read and manage Facebook and Instagram campaigns, ad sets, ads, audiences and insights from your agent, preview an ad per placement, and estimate reach before you spend. New campaigns are created paused.
  • Google Ads MCP. Search and Performance Max campaigns, ad groups, keywords and keyword ideas, assets, conversion actions, budgets and reports.
  • TikTok Ads MCP, and the same campaign tools for LinkedIn Ads, Reddit Ads, Pinterest Ads, Snapchat Ads, Microsoft Advertising, X Ads, Apple Ads and ChatGPT Ads.
  • AI ad generator API. Image, video and UGC avatar ads with your real product, copy and CTA, from render_ad, generate_image and generate_video, the CLI, or the REST API at /v1 (OpenAPI).
  • Ad spy. Search the Meta, Google and LinkedIn ad libraries and organic TikTok, Instagram, YouTube, Reddit and Threads, and turn a competitor's longest-running ads into angles you can use.
  • Posting. Post and schedule to your own social channels, then read each post's results. The channel list is under What it connects to below.

Set up in Claude, ChatGPT or Cursor

Claude (claude.ai, Claude Desktop): Customize → Connectors → Add → Add custom connector, paste the URL below, choose Sign in now under Authentication, then Add and Connect.

text
https://app.hermoso.ai/mcp

Claude Code:

bash
claude plugin marketplace add hermoso-ai/hermoso && claude plugin install hermoso@hermoso

ChatGPT: turn on Developer mode once (Settings → Security and login), then Plugins → + → Create app → Create MCP App. Paste https://app.hermoso.ai/mcp as the Server URL, choose OAuth, leave the advanced OAuth settings empty, and press Create.

Cursor:

bash
npx skills add hermoso-ai/hermoso -a cursor && npx -y hermoso auth login

Or add the MCP server to Cursor's mcp.json after npx -y hermoso auth login:

json
{ "mcpServers": { "hermoso": { "command": "npx", "args": ["-y", "hermoso", "mcp"] } } }

Codex, Gemini CLI, Grok and the rest are in Install in one command.

Examples

Ask in your own words. The agent picks the tools:

  • "Pull the longest-running Meta ads for allbirds.com and tell me the three angles they keep paying for."
  • "Make a 15-second UGC video ad for our product in 9:16, with a voiceover and captions."
  • "Make four static ads for the new collection, one per Instagram placement."
  • "Build a Meta Ads campaign for this video with the daily budget I give you, leave it paused, and show me the preview."
  • "Compare last week's Google Ads and Meta Ads results and tell me what to pause."
  • "Schedule this video to TikTok, Instagram Reels and YouTube Shorts for Tuesday at 9am."

1182 tools. tools/list is always the authoritative set; hermoso_capabilities (free) returns the live model catalog with exact per-render credit costs plus the full capability map.

No markup on models on Starter, Pro and Studio. Stop overpaying markups on AI models. Plans: hermoso.ai/pricing.

A Chrome extension too. Hermoso: save and clone ads saves any ad to your swipefile in one click, clones it for your brand, and pulls any company's ads from its website.

Most of it costs nothing. Publishing and scheduling posts, building and managing paid campaigns, analytics and insights, comments and DMs, connectors, profiles and team seats cost no credits on any plan, with no per-post fee and no seat pricing. The Free plan includes 10 posts a month on up to 3 platforms; every paid plan posts without a limit and runs autopilot. Basic ($9.99 a month) is unlimited posting and Autopilot with no monthly credits: it includes the same 250+ free credits as the Free plan, monthly credits start on Starter, and Autopilot spends credits. Credits are spent only on running an AI model (image, video, voice, text, planning, post-production) and on Ad Spy research, and posting an ad you already rendered is never a second charge. The one exception is X (Twitter), where posting and reads bill credits per call because X charges per API request (hermoso_credits prints the exact credits per X post).

What it connects to. Ad platforms: Meta, Google Ads, TikTok Ads, LinkedIn Ads, Reddit Ads, X Ads, Pinterest Ads, Snapchat Ads, Microsoft Advertising, Apple Search Ads, ChatGPT Ads and AppLovin Ads, plus product feeds in Google Merchant Center. Publishing and scheduling — fifteen channels: Facebook, Instagram, Threads, TikTok, YouTube, X, LinkedIn, Pinterest, Bluesky, Telegram, WordPress, Discord, Slack, Whop and Google Business Profile. Messaging: WhatsApp (you message a person, so it is not a sixteenth publishing channel) and DM automations on Instagram, Facebook Pages and Messenger, and X (a keyword comment, a DM, a story reply or an ig.me / m.me link gets an automatic reply). Ad research: the Meta, Google and LinkedIn ad libraries plus organic TikTok, Instagram, YouTube, Threads and Reddit. Analytics: Google Analytics 4, Google Search Console and every connected platform's own post and campaign insights. Files: Google Drive, Sheets, Docs and OneDrive.

It is not all-or-nothing. Research, creation, publishing/scheduling and ads management are four independent areas — no tool requires that you used another one first. Publish or schedule creative you already have and generate nothing here (upload_file turns any local or external file into a URL every publish, schedule and ad-build tool accepts); build and read campaigns on your own ad accounts with your own creative; research competitors with no brand drafted and no channel connected; or generate a file with nothing connected at all and just download it. Use the one piece you need, or all of it together.

Install in one command

This repo is a plugin marketplace, a Gemini CLI extension and a skills package at once, so a coding agent takes Hermoso in one line. Every install brings the same five skills (hermoso-research, hermoso-generate, hermoso-ad-from-brand, hermoso-product-photoshoot, hermoso-marketing), and the skills drive the hermoso CLI through npx. No tool list is loaded into your session: a CLI command costs nothing until it runs, and it reaches every tool. The first time, your agent runs npx -y hermoso auth login, which opens a browser to sign in.

Claude Code:

bash
claude plugin marketplace add hermoso-ai/hermoso && claude plugin install hermoso@hermoso

Inside a session the same thing is /plugin marketplace add hermoso-ai/hermoso then /plugin install hermoso@hermoso.

Codex CLI (it reads the same marketplace file):

bash
codex plugin marketplace add hermoso-ai/hermoso && codex plugin add hermoso@hermoso

Gemini CLI:

bash
gemini extensions install https://github.com/hermoso-ai/hermoso

Grok (xAI) comes in three shapes, and each one connects differently:

  • grok.com and the Grok apps take the hosted connector: open Plugins → Connectors → New Connector → Custom (or go straight to grok.com/connectors), paste https://app.hermoso.ai/mcp and sign in with your Hermoso account.

  • Grok Bot: add the Hermoso Marketing Bot template. It arrives wired to Hermoso and asks for a key, which you create in the app under MCP & CLI → Terminal & API keys.

  • Grok Build, xAI's terminal agent, runs a local MCP server. Sign in once, then add it:

    bash
    npx -y hermoso auth logingrok mcp add hermoso -- npx -y hermoso mcp

Cursor, OpenCode, GitHub Copilot, Windsurf and about 75 more agents, through the open skills installer:

bash
npx skills add hermoso-ai/hermoso

It asks which agents to install into; -a opencode (or cursor, github-copilot, windsurf) picks one, and --skill hermoso-research installs a single skill. VS Code agent plugins can also take this repo whole: run Chat: Install Plugin From Source and paste https://github.com/hermoso-ai/hermoso.

ChatGPT and Claude.ai run in a browser and cannot run a CLI, so they use the hosted connector instead (see below). The ChatGPT desktop app and ChatGPT workspace admins can also read this repo's .claude-plugin/marketplace.json, which installs the skills.

No browser on the machine? Create a key in the app under MCP & CLI and run npx -y hermoso auth login --token <key>.

On a server, a CI job or an agent gateway that calls Hermoso many times a day, install it once (npm i -g hermoso) and call hermoso directly. npx asks the npm registry about the package on every run, and a busy machine can be rate-limited by npm. If the CLI ever says an HTTP error "did not come from Hermoso", something on your side (a proxy, a firewall or the gateway) refused the request before it reached us.

Which surface should your agent use?

Two shapes, and the right one is decided by what your client can do, not by which we prefer.

Your clientUseWhy
Runs in a browser: Claude.ai, ChatGPT, Claude Desktopthe hosted connector https://app.hermoso.ai/mcpIt cannot spawn a local process, so a URL is the only shape it has. Nothing to install, no key to paste, and the full toolset arrives with your saved brand context. This is the right answer for these clients, not a lesser one.
Can run a shell: Claude Code, Codex, Gemini CLI, Cursor, Cline, OpenClaw, Hermes, your own scriptsthe one-command install above, or the CLI itself (npm install -g hermoso)A tool manifest is loaded into every session whether or not a tool is called. A shell command costs nothing until it runs, and it reaches every tool rather than the default roster.

The CLI answers the questions a tool manifest would, on demand and only when asked:

bash
npx -y hermoso tools --search reddit   # every matching tool, name + one linenpx -y hermoso tools plan_ad           # one tool's full argument schemanpx -y hermoso call plan_ad --json '{"product":"…"}'   # run it

tools and tools <name> read a registry bundled in the package (no key, no network, no sign-in), so an agent can browse the entire product before anyone signs in. Only call spends, and only that needs hermoso auth login once.

Want the tools in your coding agent's own list as well? That is the MCP server, and it is optional. One hermoso auth login covers the CLI and lets claude mcp add hermoso -- npx -y hermoso mcp pick the key up with no env block, so the agent can reach for a native tool when it wants structured results and shell out when it wants breadth. It costs context in every session, so add it when a session settles into one area and makes many calls there; enable_tools({groups:['ads']}) then turns campaign management on in a single free call.

Your agent can sign itself up

An agent with no Hermoso account can provision one, get its own key, and be rendering ads in the same session. No human at a browser, no ticket, no waiting.

bash
# 1. Start a signup. This call takes no credential, because the credential is what it creates.curl -sX POST https://app.hermoso.ai/v1/signup \  -H 'content-type: application/json' \  -d '{"plan":"pro","period":"mo","email":"[email protected]"}'# -> { "id": "cs_...", "checkout_url": "https://checkout.stripe.com/...", "claim_token": "hsc_...", "email": { "address": "[email protected]", "verified": false } }# email = the human behind the account. A verification link goes there; the account works before it is clicked.# It is a contact mailbox only, never a sign-in. GET /v1/account/email reports the state; POST /v1/account/email/resend re-sends or changes it.
# 2. Pay at checkout_url. Store claim_token first: it is returned only in that response.
# 3. Claim it. Poll until status is "ready".curl -sX POST https://app.hermoso.ai/v1/signup/cs_.../claim \  -H 'content-type: application/json' \  -d '{"claim_token":"hsc_..."}'# -> { "status": "ready", "api_key": "hmk_...", "credits": 3000 }

That hmk_ key is the same credential everything else on this page takes: /v1, the MCP server, the CLI. Point your client at it and the full surface is open.

Paying is something a browser-capable agent can already do itself. Checkout is Stripe's own hosted page, so Claude in Chrome and clients like it complete it unattended today. Everything else is a one-click handoff: send checkout_url to whoever holds the card. The same shape covers you later, once you are running: buy_credits and upgrade_plan mint a ready-to-pay link for more credits or a bigger plan, and billing_status reads the balance any time.

An agent with its own payment credential can pay with no human at all. POST /api/billing/machine-payment with {"packId": "pack-1k"} answers HTTP 402 carrying a WWW-Authenticate: Payment challenge (Stripe, through the Machine Payments Protocol); pay the challenge and retry, and the same credit pack lands on the same balance. GET /api/billing/config lists the packs under machinePayments. Same packs, same prices, no per-call billing.

The agentic path takes a paid plan. Any of them. The free plan is there for a person signing up at app.hermoso.ai, and asking for it here returns a refusal that says so. Nothing is created until the payment completes, so an unpaid signup leaves no account behind and charges nothing.

One thing still wants a person, and it is worth knowing up front. Connecting a social or ad account means an OAuth consent screen, and a consent screen cannot be completed headlessly on any platform. list_connectors shows what is already connected and what is not. Everything else runs with no browser at all: research, generation, publishing to a channel that is already connected, campaign builds, reporting.

Full request and response shapes, plus every other endpoint, are in the OpenAPI document at app.hermoso.ai/openapi.json, served live from the same table that mounts the routes.

Instant: the hosted Claude.ai connector

Paste https://app.hermoso.ai/mcp?src=readme into Claude → Customize → Connectors → Add → Add custom connector, press Continue, choose Sign in now under Authentication (Claude's detector pre-selects "No sign-in" because our discovery handshake is open, and with that your first request comes back "Authentication required"), press Add and Connect, approve with your Hermoso account, and you are done: the full toolset with your saved brand context, billed to your plan.

Quickstart for Claude Code (one command)

  1. Get an account at app.hermoso.ai. The free tier is included; plans and credits are the same ones the web Studio uses. Or skip the browser entirely and let your agent sign itself up on a paid plan with POST /v1/signup (above).
  2. Install the plugin. It adds the five Hermoso skills, which drive the hermoso CLI through npx:
bash
claude plugin marketplace add hermoso-ai/hermoso && claude plugin install hermoso@hermoso

Already inside a session? /plugin marketplace add hermoso-ai/hermoso, then /plugin install hermoso@hermoso. Codex, Gemini CLI and the rest are in Install in one command. 3. Sign in once. npx -y hermoso auth login opens your browser; your agent also runs it by itself the first time it needs Hermoso. On a machine with no browser, use npx -y hermoso auth login --token hmk_… with a key from the MCP & CLI tab, under Terminal & API keys (Settings → API keys, or https://app.hermoso.ai/?tab=api-keys, go straight there). 4. Ask for what you want, in your normal prompts. Claude Code picks the Hermoso skill for the job and runs the commands. You type none of them.

Rather install the CLI by hand? npm install -g hermoso puts the same hermoso command on your PATH, and the skills use it when it is there.

The hosted URL works in Claude Code too, but the plugin is the lighter path there because it loads no tool list into your sessions. If you want the connector: claude mcp add --transport http hermoso "https://app.hermoso.ai/mcp?src=readme", and claude mcp list reports ! Needs authentication until you run claude mcp login hermoso once and approve in your browser (--no-browser prints the link on a headless machine). Measured against Claude Code 2.1.282 on 2026-09-25.

Your agent now has the full studio with your profile's context: the brand details, products, logos and learned memory you set up in the web app apply automatically (get_brand shows what's saved; omit brand in plan_ad/plan_variations to use it). Renders bill your Hermoso credits, at the same prices as the Studio. Only AI model runs and Ad Spy research spend credits; publishing, scheduling, ads management and analytics cost no credits on any plan (posting to X and reading X data are the one per-call exception, managing X ads is free). The Free plan includes 10 posts a month on up to 3 platforms; every paid plan posts without a limit and runs autopilot. Basic ($9.99 a month) is unlimited posting and Autopilot with no monthly credits: it includes the same 250+ free credits as the Free plan, monthly credits start on Starter, and Autopilot spends credits.

1. MCP server (stdio), optional in a coding agent

hermoso mcp runs a stdio MCP server exposing the full toolset, for a client that wants Hermoso's tools in its own list. The published hermoso package means no clone: npx -y hermoso mcp fetches and runs it. Sign in once with the CLI and no key goes into any client config, because hermoso mcp reads the bearer hermoso auth login stored:

bash
npm install -g hermoso && hermoso auth login && claude mcp add hermoso -- npx -y hermoso mcp

On a machine with no browser, skip the sign-in and pass the key to the client instead:

bash
claude mcp add hermoso -e HERMOSO_TOKEN=hmk_… -- npx -y hermoso mcp

Cursor / Codex: sign in the same way, then add to mcp.json (Codex uses the TOML equivalent). Drop the env block entirely if you signed in above; it is there for CI, where the process cannot read your home directory:

json
{ "mcpServers": { "hermoso": { "command": "npx", "args": ["-y", "hermoso", "mcp"],  "env": { "HERMOSO_API_BASE": "https://app.hermoso.ai", "HERMOSO_TOKEN": "<your token>" } } } }

Then ask your agent: “Generate an image ad with Hermoso.”

What the 1182 tools cover

Ad spy / research — find_competitors, competitor_teardown, pull_competitor_ads, research_ads; the Meta / Google / LinkedIn ad libraries (search_meta_ads, search_google_ads, search_linkedin_ads); organic social (search_tiktok, search_instagram, search_youtube, search_reddit, search_threads); fetch_social_data, mine_angles, analyze_video, check_ad_policy, list_skills / get_skill.

Create — draft_brand → plan_ad → render_ad (the Studio quality pipeline: composited text, clean speech, music, brand end card), or generate_image / generate_video / generate_avatar (UGC creators + lip-sync). The workspace's saved cast is reusable: list_creators returns every saved creator with their portrait url, save_creator adds one, delete_creator drops one — re-pass a portrait to generate_avatar / generate_video / recast_motion and the SAME person stars in every ad, instead of a new face each render. Also make_template_ad (native HTML ad formats), make_explainer, product_sizzle, make_thumbnail, clone_static, recast_motion, reframe_video, upscale_video, dub_video, change_voice, finish_video, fix_beat, stitch_video, clip_video, post_edit, plus plan_variations + score_ad to fan out and rank. Length is yours to set: pass durationSeconds to plan_ad and the storyboard is authored to it — a length that fits one clip of the render model renders as a single continuous take, longer is stitched from acts (on a 15s-clip model, 40s = 15+15+10), never time-compressed. What fits one clip is the model's own maximum, not a fixed number: most video models cap a clip at 15 seconds and the longest-clip one takes 30 seconds in one unbroken take with native synchronized audio. hermoso_capabilities is the live list — durations, resolutions and the exact credit cost of every tier — and naming that model in model is how you get it, since an unnamed render is routed by a narrower auto-pool.

Ads Studio (make_static_ads): pick products and a count and get a batch of finished static ads in one call, each a different selling angle and a different layout, with your real product photo and logo laid on. The product catalog it draws from (list_products, add_product, refresh_products, update_product, remove_product) is a cache of each product's live page, so names, photos and prices come from the page itself.

Swap a person into any video (recast_hook, recast_motion): keep the scene, the cuts and the sound and change who is on screen, using your own photo, a saved creator or an AI person (in the web Studio you can describe a presenter in words). Real faces from your own photos need a paid plan; AI people work on every plan.

Raw model playground — the full catalog (30+ image / video / voice / writing models, each with its exact per-render credit cost) with no ad framing: generate_image / generate_video with useBrand:false, generate_voice, generate_text. The image default is Nano Banana 2.1.

Publish to your own channels — fifteen of them: Facebook, Instagram and Threads (post_to_meta), TikTok (post_to_tiktok), YouTube (post_to_youtube + update_youtube_video, youtube_video_insights, comments read/reply), X (post_to_x, x_post_metrics, x_post_insights, x_mentions, list_x_dms, send_x_dm), LinkedIn profile and company Pages (post_to_linkedin, post_to_linkedin_page), Pinterest (post_to_pinterest + boards), Bluesky (post_to_bluesky, delete_bluesky_post, bluesky_post_metrics, plus list_bluesky_convos / read_bluesky_dm / send_bluesky_dm) and Telegram (post_to_telegram, delete_telegram_message, list_telegram_chats), WordPress (post_to_wordpress, update_wordpress_post, delete_wordpress_post, list_wordpress_posts), Discord and Slack announcements (post_to_discord, edit_discord_message, delete_discord_message, post_to_slack), Whop (post_to_whop, edit_whop_post, delete_whop_post, get_whop_post, list_whop_destinations) and Google Business Profile (below). schedule_post / list_scheduled / cancel_scheduled give you one content calendar over exactly that set. upload_file brings in any external or local media, not just Hermoso renders. X posting bills credits per API call (X charges per request); a post containing a link costs 13× one without. Google Business Profile is the panel a business shows on Google Search and Maps. post_to_google_business publishes a Post with text, optionally one photo, and a call-to-action button (Google takes no video in Posts, so a schedule with only a video is refused for that channel); list_google_business_posts and delete_google_business_post manage what is showing. list_google_business_reviews finds the reviews with no answer yet and reply_to_google_business_review answers one as the business. google_business_insights reports search and Maps impressions, calls, website clicks and direction requests, and google_business_search_keywords the words people searched to find the listing. list_business_locations, get_business_location and update_business_location read and edit the listing itself: hours, phone, website, description, categories. Business Profile Q&A is not offered: Google retired it on 2025-11-03.

Message customers on WhatsApp — messaging, not a twelfth publishing channel: you message a person, and nothing here posts to a feed. list_whatsapp_accounts finds the Business Account and its numbers, list_whatsapp_templates / create_whatsapp_template / delete_whatsapp_template manage the templates Meta reviews, and send_whatsapp_message sends one — confirm-gated, because it reaches a real phone and Meta bills the business for the conversation. Two limits that are permanent facts about Meta's API rather than anything pending: Hermoso does not receive WhatsApp webhooks, so there is no message history to read — it is not an inbox surface and list_inbox does not cover it — and outside the 24-hour window that opens when the customer messages first, WhatsApp accepts an APPROVED template and nothing else.

Automate Instagram DMs — save_instagram_dm_automation sets up a rule that answers people for the brand: a comment with a keyword on one post, the next post or any post gets a private reply, and a DM, a story reply or an ig.me link gets an answer, with optional link buttons, a public comment reply, a follow gate and a dry run. test_instagram_dm_automation shows exactly what a sample comment or DM would send without sending it, and list_instagram_dm_automations / delete_instagram_dm_automation read each rule's stats and send log or remove it. Each person gets one reply per post, Meta allows a private reply within 7 days of the comment, and no AI runs on the send path, so it costs no credits.

Automate DMs on Facebook and X too — save_dm_automation (with test_dm_automation, list_dm_automations and delete_dm_automation) takes a channel: instagram, facebook (a keyword comment on a Page post or an ad gets a private reply in Messenger; a Messenger keyword or an m.me link gets an answer) or x (a keyword @mention or a reply under your post gets a public reply; a DM keyword gets a DM back). X only allows an automated DM after the person has DMed you, and a DM saying STOP opts that person out. Facebook is free like Instagram. X bills us per call, so an X automation is charged in credits for every mention, reply or DM X delivers plus each reply or DM sent, and running out of credits stops its X rules.

Autopilot posting (set_post_refill, get_post_refill, run_post_refill): say how often, where and what about ("2 posts a day on Instagram and Bluesky about our slow-morning routine, let me review first") and Hermoso makes new posts from your brand and fills your calendar. Pick review first (each batch waits for your approval, edit or redo) or post automatically (straight onto the schedule, still pullable). It follows the notes you leave and writes from what has worked on each channel. Credits are used only when a post is made.

Run the ads — full campaign trees, built paused and read back before anything is reported, with every spend change confirm-gated, on twelve platforms: Meta, Google Ads, LinkedIn Ads, Reddit Ads, Pinterest Ads, Microsoft Advertising, ChatGPT Ads (OpenAI's Advertiser API), X Ads, TikTok Ads, Snapchat Ads, Apple Ads (Apple Search Ads on the App Store) and AppLovin Ads (reporting and the website pixel on the keys every account has; campaign building once AppLovin enables Campaign Management API access on the account, and server-side conversion events with the Conversion API key AppLovin issues on request). Each has list + report + create + budget/status tools (e.g. list_google_ads_campaigns, google_ads_report, create_google_ads_campaign, set_google_ads_budget, set_google_ads_status). Snapchat needs one extra step the others do not: an ad points at a CREATIVE, and every Snapchat creative must carry a Public Profile id — build it with upload_snapchat_ads_creative.

Feed the shopping surfaces — Google Merchant Center is the catalog a retail Performance Max or Shopping campaign advertises (create_google_ads_performance_max_campaign takes a merchantCenterId), and you manage it from here: accounts and account status, data sources, product upsert / update / delete, per-region inventory, quota, merchant_report for product-level performance, notifications and conversion sources, plus the disapproval loop — list_merchant_issues says what is wrong and merchant_issue_help returns Google's own documented fix. Promotions need the merchant's own enrolment in Google's promotions program; without it Google refuses that sub-API outright. Microsoft Merchant Center is covered on the same shape (stores, catalogs, products, issues) for Bing Shopping.

Measure what the ads achieved — Google Analytics 4 closes the loop. Every other connector here reports what an ad cost; this is the one that reports what it did. analytics_report breaks sessions, users, conversions and revenue down by channel, source/medium, campaign, landing page, country, device or date, so the campaign Hermoso built and the revenue it drove sit in one conversation. analytics_realtime shows who is on the site right now. Start at list_analytics_properties — the tools take a numeric property id, not the G-XXXXXXXXX Measurement ID from your tracking snippet, and this is what resolves one from the other. It writes as well as reads: create_analytics_key_event marks an event GA4 already collects as a key event — which is what makes it importable into Google Ads as a conversion — and create_analytics_custom_dimension registers an event parameter so reports can break down by it, with list_analytics_definitions showing what the property already measures. It signs in with the same Google account as Google Ads, YouTube and Drive, but it is its own connection. GA4 only — the API has no Universal Analytics surface. A custom dimension can be archived but never deleted, and a property holds 50 event-scoped ones.

Files — Google Drive CRUD (save_to_drive, list_drive_files, update_drive_file, delete_drive_file, create_drive_folder), Google Sheets (create_sheet, append_to_sheet, read_sheet), Google Docs (create_doc, append_to_doc), and OneDrive (save_to_onedrive + full CRUD).

Workspace & account — profiles (list_brands, create_brand, use_brand, update_brand, delete_brand; one account holds many profiles, each a brand, a client, a creator or a personal workspace, so an agency runs every client through here), memory (remember, forget, list_memory), custom skills (save_skill, get_skill, list_skills, delete_skill — the one library, which absorbed the old AI-Employee personas), team (list_team, invite_member, remove_member, set_role), settings (get_settings, update_settings — including the language every ad, script and plan is written in), connectors (list_connectors, list_connector_accounts, set_connector_accounts, disconnect_connector), and billing (hermoso_credits, billing_status, buy_credits, upgrade_plan, set_auto_reload), plus list_jobs / get_job for async renders.

Connector accounts are picked, not guessed. One person often administers several Facebook Pages, Google Ads customers or LinkedIn company Pages. Only the accounts ticked for a profile are usable — enforced server-side, and an empty selection shares nothing. Linking a new account is the one step that is not headless (it is an OAuth consent screen, so the user does it in the app).

Render jobs queue server-side and poll to completion, returning a served URL.

2. CLI: the context-free path for terminal agents

bin/hermoso.mjs exposes the full MCP toolset as subprocess commands, so an agent can shell out instead of carrying a fat tool manifest.

bash
npm install -g hermoso                             # installs `hermoso`hermoso capabilities                               # valid model ids + costs (run first)hermoso create --brand "YourBrand" --product "your best-selling product" --format imagehermoso generate image --prompt "…" --ref ./product.png --waithermoso generate video --prompt "…" --duration 8 --waithermoso competitors yourbrand.comhermoso research "Liquid Death’s longest-running ads"

Add --json to any command for machine output.

Those shortcuts are the common path, not the limit. Every tool the MCP server has is reachable here too, including the ad-campaign and analytics groups a connector leaves out of its default roster:

bash
hermoso tools                          # every tool, grouped, name + one linehermoso tools --group ads --search reddit   # narrow ithermoso tools create_meta_campaign     # that tool's full argument schemahermoso call create_meta_campaign --json '{"name":"…"}'   # run ithermoso create_meta_campaign --name "…"                   # same thing, shorter

call goes through the same handler, the same argument validation and the same confirm/spend gates the MCP server uses, so there is no second implementation to drift. tools and tools <name> read a registry bundled in the package, so they need no key, no network and no sign-in.

3. Skills: what a coding agent installs

skills/ holds five installable skills:

SkillWhat it does
hermoso-researchFind competitors, pull their real running ads, surface the hooks worth copying
hermoso-generateA prompt to a finished image, video, avatar clip or stitched cut
hermoso-ad-from-brandA brand or domain to one on-brand ad, concept and copy included
hermoso-product-photoshootA real product photo composited into studio, lifestyle or hero scenes
hermoso-marketingThe whole loop: research, create, publish and schedule, paid campaigns, measure

They are what every one-command install at the top of this page adds, in Claude Code, Codex, Gemini CLI and through npx skills add. From a clone, copying works too:

bash
cp -r skills/* ~/.claude/skills/

Then invoke /hermoso-ad-from-brand an ad for yourbrand.com, our hero product.

Ask for it in your own words

The skills pick themselves. These are whole prompts, not commands:

  • Find my top 5 competitors for [product + URL], pull their best Meta and TikTok ads from the last 90 days, and tell me the 3 hooks and 2 formats worth stealing, with the evidence.
  • Make this week's ads for [brand]: two hooks in two formats, one UGC and one product visual, 9:16. Score them and policy check them before I ship.
  • Here is a competitor ad: [link]. Break down why it works, then rebuild the structure with my product, my branding and a fresh hook.
  • Fill my social queue for the next 7 days across Instagram, TikTok, X and LinkedIn, repurposed from my best post, with per-channel captions.
  • Take last week's winner and build a $20/day test campaign on Meta and TikTok, three ad sets, one angle each. Leave it paused and read the tree back so I can check it.
  • Pull last month's campaign performance across every platform. Which two creatives won, why, and what should next month's brief say?

Configuration

EnvMeaning
HERMOSO_API_BASEThe Hermoso API origin (default https://app.hermoso.ai — set http://localhost:3000 if you run the app yourself)
HERMOSO_TOKENBearer agent key (hmk_…) — required against the hosted app
HERMOSO_PROFILEProfile id, for accounts with multiple profiles
HERMOSO_OWNEROnly for a profile another account shared with you (a team workspace): the owning account id. Set it together with HERMOSO_PROFILE, and set HERMOSO_PROFILE to that workspace's profileUuid (a profile's short slug is refused). Run list_brands (or hermoso list_brands from the CLI) to print both values for every workspace you can enter. The server re-authorizes the pair on every request, so a wrong value is refused, never trusted.

mcp/http.mjs is the hosted remote-connector transport (paste-a-URL into Claude.ai → Connectors). It ships in this repo for transparency and refuses to mount without authenticated identity — no anonymous spend, ever.

License

MIT © Hermoso

來源:README.md,提交 9ae00bc

工具

200

enable_tools

Turn on more Hermoso tools

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.

輸入參數groups必填

find_tools

Find a Hermoso tool by name or task

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.

輸入參數grouplimitqueryonlyHealthy

call_tool

Run any Hermoso tool by name

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.

輸入參數argsname必填

post_to_bluesky

Post to Bluesky

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 and says which limit. @MENTIONS written as the full handle (@alice.bsky.social) are looked up and tagged as Bluesky mentions; one that does not resolve stays plain text and the reply names it. MEDIA: either up to 10 images (imageUrls + altText; 5–10 post as a Bluesky gallery, more than 10 is refused, never cut; a picture over Bluesky's 2 MB limit is re-encoded to fit and the reply says so) OR one MP4 video (videoUrl + videoAlt), never both. 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 > Bl

輸入參數hooktext必填brandlangsideaIdrecipeaccountaltTextsubjectcaptionslinkCardvideoAlt另有 5 個

post_to_telegram

Post to Telegram

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

輸入參數hooktextbrandchatId必填ideaIdrecipesilentaccountbuttonssubjectimageUrlvideoUrl另有 7 個

list_telegram_chats

Find Telegram chat ids

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.

輸入參數limitaccount

hermoso_capabilities

Hermoso models, options and credit costs

Probe what this Hermoso account can do RIGHT NOW: available image/video model ids + their credit costs (estimates: a render is charged its actual cost), aspect ratios, video durations, the recipe ids, and the canEdit/canAvatar flags. Call it when you need a specific model id, a credit cost, or a capability you are not sure of. It is NOT a prerequisite for rendering: generate_image, generate_video and render_ad all run with `model` omitted and route to the server’s own default. Read-only, free.

無需輸入參數。

hermoso_credits

Credit balance

Return the credit balance, recent priced calls with their credits, and costModel (the one-sentence rule of what costs credits). THE RULE: only AI model runs and Ad Spy research spend credits; publishing, scheduling, ads management, analytics, comments, DMs and connectors cost no credits on any plan (X is the single per-call exception; xCost gives its exact credits per post, with and without a link). Check before kicking off paid generation; answer "does posting cost credits?" with NO. HOW MANY posts is set by the plan, not credits: the Free plan includes 10 posts a month on up to 3 channels, and a post counts once however many channels it goes to (`posting` says how many are used); every paid plan posts without a limit and runs autopilot (Basic $9.99/mo: unlimited posting and Autopilot, no monthly credits; it includes the same 250+ free credits as the Free plan, monthly credits start on Starter, and Autopilot spends credits). Starter, Pro and Studio: media models at cost + 10% (memberRate); Basic and Free pay standard prices.

無需輸入參數。

buy_credits

Buy credits

Out of credits? Top up with a credit PACK. Call with no argument to list the available packs (id · credits · price). If the account has a saved card and you have billing-admin rights, calling with `pack` quotes the exact charge and calling again with confirm:true AND the quote's quote_token charges the saved card instantly (same one-click top-up as the app — no redirect). If there's no saved card yet, you get a Stripe checkout URL to hand your human for the FIRST purchase; their card saves for one-click after that. Packs only; subscriptions are managed by a person in Billing. IF YOU ARE AN AGENT HOLDING YOUR OWN PAYMENT CREDENTIAL, there is a third path that needs no human at all: `POST /api/billing/machine-payment` with a `packId` answers HTTP 402 carrying an MPP challenge, and grants the pack once you authorise and retry with the credential — the same packs, the same prices, the same credits. `GET /api/billing/config` carries a `machinePayments` block listing the packs with their per-credit rates and saying whether that lane is enabled on this server. Most agents do NOT have their own credential yet, so the checkout link above remains the normal path. To stop running out entirely, turn on auto-reload with set_auto_reload (admin) — low balances then top themselves up from the saved card automatically.

輸入參數packconfirmquote_token

report_bug

Report a bug

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

輸入參數details必填summary必填severity

request_feature

Request a feature

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

輸入參數details必填summary必填

billing_status

Billing status

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. posting: this month's posting allowance ("N of 10 posts used this month" on Free, unlimited on every paid plan).

無需輸入參數。

upgrade_plan

Upgrade plan

Change the SUBSCRIPTION plan (admin only). No argument lists the monthly plans. Basic ($9.99) is the posting plan: unlimited posting and Autopilot with NO monthly credits (it includes the same 250+ free credits as the Free plan, monthly credits start on Starter, and Autopilot spends credits). Starter, Pro and Studio include monthly credits and no markup on models. Every paid plan posts without a limit (Free: 10 posts a month on 3 channels). New subscriber: a Checkout URL for the human. Existing subscriber upgrading: a quote + quote_token; once the human agrees, call again with confirm:true and quote_token to charge the card on file. Downgrade: a billing-page link; it starts at the term's end.

輸入參數planperiodconfirmquote_token

set_auto_reload

Set auto-reload

Turn automatic credit reloads on or off (admin only): when the balance drops below a threshold, the card on file is charged for a top-up pack — SERVER-SIDE, even with no app open. Requires a saved card, added once in the app at first checkout/top-up; if there's none the tool tells you exactly where to add it. After that one-time card setup, agents can manage auto-reload, top-ups and plan links fully. Members (read-only billing) get an 'ask an admin' message.

輸入參數enabled必填reloadCreditsthresholdCredits

list_brands

List profiles

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.

無需輸入參數。

use_brand

Switch profile

Pin which profile this connection acts on. Pass the profile id or exact name from list_brands. Works for your own profiles AND one another account SHARED with you (pass its name or the profile id list_brands prints); access is verified against your real membership before it is pinned. Persists for this API key until changed, on every surface (hosted connector included — no environment variables, no restart).

輸入參數brand必填

create_brand

Create a profile

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.

輸入參數name必填activate

delete_brand

Delete a profile

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.

輸入參數brand必填confirmconfirmNameconfirmConnectors

list_meta_pages

List Meta pages & ad accounts

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.

無需輸入參數。

upload_file

Upload a local file -> durable public URL

Persist an ARBITRARY user file (image, video, audio, PDF or document, up to 150MB) into Hermoso and get back a durable public URL that EVERY publish, schedule and ad-build tool accepts — post_to_meta / post_to_linkedin / post_to_linkedin_page / post_to_youtube / post_to_tiktok / post_to_pinterest / post_to_x / post_to_google_business / schedule_post / upload_meta_asset / upload_google_ads_asset / create_meta_ad / create_linkedin_ads_creative / set_youtube_thumbnail / save_to_drive / save_to_onedrive. Kept in the Library. THIS IS THE BRING-YOUR-OWN-CREATIVE PATH: it is for files that have NOTHING to do with a Hermoso render (media on the user's desktop, an agency's finished ad, a photo they shot), and it means you can publish, schedule and run ads through Hermoso without generating anything here. Provide exactly ONE source — passing two is an error, never a silent preference: `url` (ANY public http(s) link — Hermoso fetches it server-side, so nothing crosses this connection and there is no practical size limit; THIS IS THE ONE THAT ALWAYS WORKS, including on the hosted connector), `path` (a local file, ONLY when Hermoso runs on the user's own machine over stdio/CLI; the hosted connector cannot see their disk), or `dataUri` (a base64 data: URI — keep it under ~15MB, since the bytes travel over this connection). If the file is already at a public https URL, the Meta, Reddit and ChatGPT-Ads tools take it directly and re-host it safely — but LinkedIn (posts and ad creatives), Pinterest, the YouTube thumbnail and upload_google_ads_asset upload the BYTES themselves and therefore refuse an external host, so run it through here first and pass the URL this returns. When in doubt, use this: its URL works everywhere. (Calling the underlying HTTP route directly? POST /api/upload takes the file's RAW BYTES as the request body with its own content-type — NOT multipart/form-data — or ?url=<link> with no body.) Returns {url, kind, bytes}.

輸入參數urlnamepathdataUrigetUploadUrl

list_whatsapp_accounts

WhatsApp Business accounts and numbers

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.

無需輸入參數。

post_to_meta

Post to Facebook, Instagram or Threads

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

輸入參數hooklinkasyncbrandplacestoryideaIdpageIdrecipetargetaccountaltText另有 46 個

schedule_post

Schedule a post for later

Use this to schedule a social media post for later, on one channel or several. Costs no credits (X alone bills per call: hermoso_credits). FREE PLAN: 10 posts a month on up to 3 channels (a post counts once; UTC month); paid plans are unlimited. Over it the post is refused and nothing is queued: relay the refusal as written, never retry or split the post. Channels: facebook, instagram, threads, tiktok, youtube, linkedin, x, pinterest, bluesky, telegram, wordpress, discord, slack. whop only where list_connectors offers it; elsewhere it is refused. A MULTI-SLIDE creative goes in imageUrls[] as a CAROUSEL, in order, never just slide 1. Name the time in `at`, or pass `useQueue:true` for the brand’s next free POSTING SLOT (“just queue it”). imageUrl/videoUrl take a Hermoso render URL or an upload_file URL. `captions`: per-channel wording; `message` for the rest. `title` (Pinterest/YouTube, max 100, derived from the caption); YouTube also `description`, `tags`, `thumbnailUrl`. Every PER-CHANNEL SETTING is a parameter below (e.g. TikTok `brandedContent` / `yourBrand`, set whenever the post is commercial; X `thread` / `poll`; Instagram `collaborators`). Channels are attempted INDEPENDENTLY: one failing never blocks the others. Hermoso never guesses the account: Pinterest needs `boardId` (list_pinterest_boards), else refused; a LinkedIn COMPANY PAGE post needs `linkedinOrganizationId` (list_linkedin_pages), else it goes to the person’s own profile. Several Facebook Pages need `pageId` (list_meta_pages): none named is refused at scheduling with the Pages listed, so ask the user which. Several Google Business listings need `locationId` (list_business_locations), and without it the post is refused when it fires. Resolve these FIRST; let the user pick. Posts GO LIVE PUBLICLY by default. Only if the user asks, set `visibility` (or `visibilityByChannel`): ‘unlisted’ (YouTube) · ‘private’ (YouTube, TikTok SELF_ONLY) · ‘draft’ (TikTok, unpublished Facebook Page post); one a channel

輸入參數athooklinkpolltagsbrandeventofferplacestorytitlechatId另有 108 個

list_scheduled

List scheduled and past posts

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.

輸入參數idbrandfiredchannelupcoming

reschedule_post

Change a scheduled post

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

輸入參數atid必填linkpolltagsbrandeventofferplacestorytitlechatId另有 105 個

cancel_scheduled

Cancel a scheduled post

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

輸入參數id必填brand

retry_scheduled

Retry a failed scheduled post

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

輸入參數atid必填brandchatIdpageIdboardIdmessagecaptionschannelslocationIdallowDuplicatewhopExperienceId另有 1 個

duplicate_scheduled

Duplicate a scheduled post

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.

輸入參數atid必填linkbrandtitlechatIdideaIdpageIdrecipeboardIdmessagecaptions另有 10 個

get_post_refill

Autopilot posting status

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

無需輸入參數。

set_post_refill

Set up, change or pause autopilot posting

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

輸入參數goalmodeavoidchatIdpageIdboardIdenabledformatspillarsaccountschannelsgoalNote另有 12 個

run_post_refill

Run autopilot now / approve its drafts

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

輸入參數editredoforcelearndryRunapprovediscard

post_to_linkedin

Publish to LinkedIn

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 (imageUrl), a VIDEO (videoUrl, with captionsSrt and a videoThumbnailUrl cover), a 2–20 image MULTI-IMAGE post (imageUrls), a LINK post with a real preview card (linkUrl + linkTitle / linkDescription / linkThumbnailUrl — LinkedIn never scrapes the page), a DOCUMENT post (documentUrl: a PDF / PPTX readers swipe through), and @mentions of companies (mentions). 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).

輸入參數hooktext必填brandtitleideaIdrecipealtTextlinkUrlsubjectimageUrlmentionsvideoUrl另有 11 個

post_to_x

Publish a post to X (Twitter)

Use this when the user wants to post to X (Twitter) now. Publish to the user’s connected X (Twitter) account — a single post, a post with an image or video render attached, a reply to an existing post, or a whole THREAD (pass `thread` as an array and each part is posted as a reply to the one before). This PUBLISHES immediately and PUBLICLY — ALWAYS show the user the exact text and get an explicit yes BEFORE calling. Can also run a POLL (2-4 options) instead of media, and restrict who may reply. LENGTH IS THE POSTING ACCOUNT’S, NOT A FLAT 280: 280 characters on an ordinary account, but UP TO 25,000 if that account has X Premium. Hermoso reads the account’s own subscription from X and sends the post rather than refusing something it may be entitled to; if X declines it on length, the reply says so and names the subscription X reported. Over 25,000 is refused for free — that is X’s own ceiling on every account. Text is NEVER truncated. AND A LONG POST IS CHEAPER THAN A THREAD: it is ONE billed X call where the same words split across five posts are five, so prefer one long post over threading when the account has Premium. Write altText whenever you attach a render. COSTS CREDITS at X’s per-post price, every post in a thread billed: a post that contains a link (any web address, and every quote post) is X’s link rate, far higher than one without (hermoso_credits gives both figures). When the user asks what a post will cost, call this with dryRun:true: it posts NOTHING and returns that exact post’s costCredits. Each profile also has a rolling 24-hour ceiling on what it can spend at X, and a request that would cross it is refused WHOLE before anything publishes, so a batch of posts is bounded rather than open-ended. THIS TOOL POSTS ORGANICALLY — it does not create an ad campaign. X ads are built with the X Ads campaign tools (campaign, then line item, then promoted post), and a promoted post needs a post id, so publish here first and promote that post. Needs X connected (S

輸入參數hookpolltextbranddryRunideaIdrecipethreadaccountaltTextsubjectimageUrl另有 15 個

post_x_article

Publish a long-form Article to X

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

輸入參數body必填hookbrandtitle必填ideaIdrecipeaccountpublishsubjectheadingsallowLossycoverImageUrl

list_pinterest_boards

List Pinterest boards

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

輸入參數accountprivacy

post_to_pinterest

Create a Pin

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

輸入參數hooklinkbrandtitleideaIdrecipeaccountaltTextboardId必填subjectimageUrlvideoUrl另有 11 個

list_business_locations

List Google business listings

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

無需輸入參數。

post_to_google_business

Post to Google Business Profile

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

輸入參數hooklinkbrandtitleideaIdrecipeendDatesubjectsummaryimageUrlstartDatetopicType另有 6 個

post_to_youtube

Post a video to YouTube

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

輸入參數hooktagsbrandtitleideaIdrecipeaccountlicenseprivacysubjectvideoUrl必填coverAtMs另有 16 個

post_to_tiktok

Post a video or photo post to TikTok

Use this when the user wants to post a video or photo post to TikTok now. Publishing costs no credits. Publish to the user’s connected TikTok account — a finished VIDEO, or a PHOTO POST (TikTok’s photo/slideshow format). A photo post carries 1 to 35 images and ONE image is simply a one-slide photo post, so there is nothing special to do for a single picture: pass imageUrls, in the order the slides should appear, and optionally coverIndex. Pass videoUrl for a video. Never pass both — TikTok has no mixed post. TWO destinations. destination:"post" (THE DEFAULT) publishes it LIVE on their profile: TikTok requires the user to CHOOSE the privacy themselves (no default is allowed), so call tiktok_creator_info, show them their real privacy options, and get their choice and an explicit yes before calling. destination:"draft" is ONLY for when the user asks for a draft, or wants to add a TikTok sound or trending audio (TikTok’s API takes no sound for a VIDEO): BEFORE sending, tell them plainly it lands in their TikTok inbox as a DRAFT, that they add the sound in TikTok’s editor, and that THEY must publish it from the TikTok app — nothing goes live until they do. Never pick draft on your own. A photo post published with destination:"post" gets a TikTok-recommended track automatically (autoAddMusic, default on) that they can change in the app. Pass Hermoso render URLs (or upload_file urls for local/external files). Needs TikTok connected (Settings > Connectors > TikTok).

輸入參數hookbrandtitleideaIdrecipeaccountprivacysubjectvideoUrlimageUrlsyourBrandcoverIndex另有 10 個

tiktok_post_status

Check whether a TikTok post finished publishing

Follow a TikTok post that post_to_tiktok sent but TikTok was still processing (it came back pending:true). Pass the publishId it returned. Answers one of three states: done (PUBLISH_COMPLETE, with the public `url` once TikTok’s moderation has named it; `urlStatus` is ready, pending, private or draft), failed (with TikTok’s reason), or still processing, which is normal for a few minutes and is NOT a failure. Never post the video again while it is processing. Free, read-only. Needs TikTok connected (Settings > Connectors > TikTok).

輸入參數accountpublishId必填

publish_to_shopify_product

Publish an image or video onto a Shopify product

Attach a finished IMAGE or VIDEO to one of the merchant's Shopify product listings, as product media. Pass productId (from list_shopify_products, the gid://shopify/Product/… form) and a PUBLIC https mediaUrl, which is what every Hermoso render returns. Whether it is a video is read from the file (.mp4 / .mov / .webm, or the link's content type); pass kind to say so outright. An image is fetched by Shopify directly; a video is uploaded to Shopify for you (MP4, MOV or WEBM, up to Shopify's 1 GB and 10-minute product-video limits; a file over a limit is refused by name, never cut). Shopify processes media asynchronously: the reply is the status read back from Shopify, and READY means it is live on the listing while PROCESSING means it will appear when Shopify finishes. That is success, not a failure. Only works for accounts created by installing Hermoso from the Shopify App Store. Free: the render was already paid for.

輸入參數altkindimageUrlmediaUrlproductId必填

list_linkedin_pages

List the LinkedIn company Pages this account administers

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.

無需輸入參數。

post_to_linkedin_page

Publish to a LinkedIn company Page

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. A DOCUMENT post (documentUrl — a PDF/PPTX deck readers swipe through) is LinkedIn’s strongest organic B2B format. Tag companies with @Name in the text plus mentions. 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).

輸入參數hooktext必填brandtitleideaIdrecipealtTextlinkUrlsubjectimageUrlmentionsvideoUrl另有 16 個

list_linkedin_lead_forms

List LinkedIn lead gen forms

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 …). Make a new one with create_linkedin_lead_form; edit or archive one with update_linkedin_lead_form. If it answers that the connection must be reconnected, say exactly that: the lead-sync permission is granted at authorise time. Read-only, free.

輸入參數pageIdadAccountId

list_linkedin_leads

List LinkedIn leads (form responses)

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.

輸入參數limitsincestartuntilformIdpageIdleadTypeadAccountIdformVersiontestLeadsOnly

get_linkedin_lead

Read one LinkedIn lead

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

輸入參數leadId必填pageIdadAccountId

subscribe_linkedin_leads

Turn on real-time LinkedIn lead delivery

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

輸入參數pageIdleadTypeforwardToadAccountId

list_linkedin_lead_events

LinkedIn lead events received in real time

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.

輸入參數limit

list_linkedin_lead_subscriptions

List LinkedIn lead webhooks

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

輸入參數pageIdleadTypeadAccountId

delete_linkedin_lead_subscription

Stop a LinkedIn lead webhook

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.

輸入參數pageIdadAccountIdsubscriptionId必填

save_to_drive

Save file(s) to Google Drive

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

輸入參數urlnameurlsfolder

import_from_cloud

Import a Drive / OneDrive folder into the Library

Pull the files in a Google Drive or OneDrive FOLDER into this profile's Library, so they can be used like anything rendered here — published, scheduled, cloned, used as a product photo or a reference. Hermoso downloads each file with the user's own connected account (a Drive/OneDrive file is not public) and stores a durable Hermoso url for each. Give `folderId` from list_drive_files / list_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.

輸入參數limitfolderIdprovider必填

list_drive_files

List Google Drive files

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.

輸入參數queryfolderIdpageSizepageTokenrecursiveonlyFoldersincludeTrashed

get_drive_file

Get a Drive file’s details

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.

輸入參數fileId必填

update_drive_file

Rename / move / trash a Drive file

Update a Drive file: rename (name), move it into a folder (moveToFolderId, optionally removeFromFolderId to move OUT of the old one), or trash / untrash it (trash:true|false). Pass fileId (from list_drive_files). To delete permanently, use delete_drive_file.

輸入參數nametrashfileId必填moveToFolderIdremoveFromFolderId

delete_drive_file

Delete a Drive file

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.

輸入參數fileId必填confirmpermanent

create_drive_folder

Create a Drive folder

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

輸入參數name必填parentId

create_sheet

Create a Google Sheet

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

輸入參數rowstitle

append_to_sheet

Append rows to a Google Sheet

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.

輸入參數rows必填rangespreadsheetId必填

read_sheet

Read a Google Sheet range

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

輸入參數rangesheetUrlspreadsheetId

export_swipefile_deck

Swipefile to Google Slides

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

輸入參數limittitlecollection

list_notion_pages

List Notion pages

Find the Notion pages and databases the user shared with Hermoso, by title, newest edited first. Gives the page id that save_to_notion, append_to_notion_page and read_notion_page take, or the database id to save rows into. Read-only, free. Needs Notion connected (Settings > Connectors > Notion).

輸入參數typelimitquerycursor

save_to_notion

Save to Notion

Save to the user's Notion as a NEW page: a research report, competitor teardown or campaign report as `content` (markdown: headings, lists, links, and an image or video URL on its own line becomes the media), a swipefile collection (swipefileCollection: name or id; each ad with its creative, copy, run dates and link), the content calendar (includeSchedule:true), or a mix. Put it under parentPageId, as a row in databaseId, or omit both for a private page in their workspace. Long text is split across blocks, never cut. The answer is read back from Notion. Free; never overwrites anything.

輸入參數icontitlecontentdatabaseIddataSourceIdparentPageIdincludeScheduleswipefileCollection

append_to_notion_page

Add to a Notion page

Add to the END of an existing Notion page (pageId or its link, from list_notion_pages): markdown content, a swipefileCollection, or includeSchedule:true. Nothing already on the page changes. Free.

輸入參數pageId必填contentincludeScheduleswipefileCollection

read_notion_page

Read a Notion page

Read a Notion page the user shared with Hermoso, as markdown (pageId or a Notion link). A database id answers with its data sources instead. Read-only, free.

輸入參數pageId必填

export_schedule_to_notion

Export the schedule to Notion

Export the posting schedule to a Notion database, one row per post: date, channels, caption, media link and status. Creates a new database (under parentPageId, or private in the workspace) or writes into an existing one (databaseId), matching its columns by name. historyDays adds posts already published. Up to 100 rows per run; the answer says if more were left. Free.

輸入參數titledatabaseIdhistoryDaysdataSourceIdparentPageId

query_notion_database

Read a Notion database

Read the ROWS of a Notion database the user shared with Hermoso — e.g. the team's content calendar: each row's title, id, link and every property as a plain value (status, date, channels, caption, links), plus the database's columns with their types and options. Filter with match {"Status": "Scheduled"} (typed from the database's own columns) or a raw Notion filter object; sort with sortBy (a column, or created_time / last_edited_time) and direction. Up to 100 rows per call, cursor for more; the answer says when Notion stopped at its 10,000-result limit. Use it to import a calendar and schedule its posts, or to find the row update_notion_row changes. Read-only, free. Needs Notion connected (Settings > Connectors > Notion).

輸入參數limitmatchcursorfiltersortByarchiveddirectiondatabaseIddataSourceId

update_notion_row

Update a Notion database row

Change properties on ONE row of a Notion database (pageId from query_notion_database) — e.g. mark a calendar row Status "Posted" and put the live post link in its URL column. properties is {"Column": value} by column name: text, number, checkbox, select (an existing option or a new one), status (must be an existing option), multi-select (array; replaces the whole value), date (ISO, or {start, end}), url, email, phone; null clears. Validated against the database's own columns before anything is sent and refused by name otherwise, never coerced; columns you do not name keep their values. Read back from Notion. trash:true moves the row to Notion's trash (restorable; returns a preview first and needs confirm:true), trash:false restores it. Free.

輸入參數trashpageId必填confirmproperties

create_doc

Create a Google Doc

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

輸入參數texttitle

append_to_doc

Append text to a Google Doc

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.

輸入參數textdropdowndocumentId必填

read_doc

Read a Google Doc

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

輸入參數docUrldocumentId

list_sheet_tabs

List the tabs in a Google Sheet

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.

輸入參數sheetUrlspreadsheetId

update_sheet

Write to a range in a Google Sheet

CORRECT cells in a Google Sheet — write values to an exact range, overwriting whatever is there. This is the fix append_to_sheet cannot make: appending only ever adds rows at the bottom, so without this a wrong number stays wrong forever and the only "correction" is a second row contradicting the first. Pass `range` (e.g. "B2:C5", or "Q3 Report!B2" to name a tab — list_sheet_tabs gives the names) and `values` as an array of row arrays; an anchor cell like "B2" is fine and the block is written down and right from it. Writing into EMPTY cells goes straight through. Writing OVER cells that already hold values is REFUSED first, naming exactly how many filled cells would be overwritten — show the user that, get a yes, then call again with confirm:true. The result is READ BACK from the sheet, so what you report is what the sheet now holds rather than what Google accepted.

輸入參數rangevaluesconfirmupdatessheetUrlspreadsheetIdvalueInputOption

clear_sheet_range

Clear a range in a Google Sheet

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.

輸入參數range必填confirmsheetUrlconfirmCellsspreadsheetId

manage_sheet_tabs

Add, rename or delete a sheet tab

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.

輸入參數tabtitleaction必填confirmnewTitlesheetUrlconfirmCellsspreadsheetId

format_sheet

Format a Google Sheet

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.

輸入參數tabsheetUrlautoResizeboldHeaderfreezeRowsspreadsheetId

update_doc

Edit a Google Doc in place

EDIT a Google Doc — the correction append_to_doc cannot make. Two shapes: `replacements:[{find, replace}]` rewrites specific text wherever it appears (call read_doc first and match the text EXACTLY; matchCase:false ignores case), or `rewrite:"…"` replaces the ENTIRE body (rewrite:"" empties it), or `dropdowns:[{title, value}]` sets a dropdown chip (e.g. Status -> Approved — read_doc lists every chip with its options; pass dropdownId when two share a title; an unknown title or option is refused with the real list and nothing changes); the same entry with options:[…] (the COMPLETE new list, 2–50) and/or newTitle changes the dropdown itself for every chip built from it, and an option you drop that a chip shows needs replace:{"Old":"New"}. Find/replace runs immediately and REPORTS how many occurrences changed — zero matches is reported as a FAILURE to match, never as a quiet success, because a text edit that silently does nothing is worse than one that visibly fails. A whole-body rewrite is destructive: call it without confirm first to get the character count, then confirm:true + confirmCells. Both are index-free by design — an agent cannot reliably compute Google’s character offsets, and a wrong offset deletes the wrong sentence.

輸入參數docUrlconfirmrewritedropdownsdocumentIdconfirmCellsreplacements

save_to_onedrive

Save file(s) to OneDrive

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

輸入參數urlnameurlsfolder

list_onedrive_files

List OneDrive files

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.

輸入參數queryfolderIdpageSizepageTokenonlyFolders

get_onedrive_file

Get a OneDrive file’s details

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.

輸入參數fileId必填

update_onedrive_file

Rename / move a OneDrive file

Update a OneDrive item: rename (name) and/or move it into a folder (moveToFolderId). Pass fileId (from list_onedrive_files). To remove an item, use delete_onedrive_file.

輸入參數namefileId必填moveToFolderId

delete_onedrive_file

Delete a OneDrive file

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.

輸入參數fileId必填confirm

share_onedrive_file

Share a OneDrive file as a link

Create a SHARING LINK to a OneDrive file or folder (fileId from list_onedrive_files): type view (read-only, default), edit, or embed (an <iframe> link, personal OneDrive only); scope anonymous (anyone with the link, default), organization (work or school OneDrive only) or users (only people you invite); optional expiresAt (date or ISO) and password (on a personal OneDrive both need a Microsoft 365 subscription; the refusal says so). An ANONYMOUS EDIT link lets anyone holding it change or delete the file, so it needs confirm:true after you show the user that; every other link is created straight away. If Hermoso already made the same kind of link, OneDrive returns that one and the reply says it was reused. Read back from the item's permission list. 0 credits. Needs OneDrive connected.

輸入參數typescopefileId必填confirmpasswordexpiresAt

onedrive_file_permissions

Who can open a OneDrive file

action list (default): every sharing link and person with access to a OneDrive file or folder: link URL, view or edit, scope, expiry, password, whether it is inherited from a parent folder. action remove: revoke one (permissionId from the list); anyone relying on it loses access at once and the link cannot be restored, so WITHOUT confirm:true nothing is removed and you get what would go. An inherited permission is removed on the parent folder, and the owner's own access cannot be removed. Removal is verified by reading the list back. 0 credits. Needs OneDrive connected.

輸入參數actionfileId必填confirmpermissionId

convert_onedrive_file

Convert a OneDrive file to PDF or JPG

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.

輸入參數widthfileId必填formatheight

create_onedrive_folder

Create a OneDrive folder

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.

輸入參數name必填parentId

plan_ad

Plan an ad concept

Plan an ad: turns a brand and a product or brief into an ad concept, with copy variants (headline, primary text, call to action) and either an image prompt or a timed video storyboard, plus the recipe and the model ids to render with. It renders nothing; pass its output to render_ad, generate_image or generate_video. When the user names a length ("a 30 second ad", "make it 45s"), pass it as durationSeconds: the storyboard is written to that length and render_ad renders it as one clip or as joined clips. Without it the planner chooses a length that suits the format, so a length the user asked for belongs in durationSeconds rather than only in the brief. The user’s own screenshots or pictures to show inside the video go in `inserts`. The brand’s SAVED product photo (get_brand / list_product_photos) is used automatically; never ask the user to upload it again. Uses credits for the planning step.

輸入參數hookbranddraftformatrecipetalentinsertsproduct必填settingconceptslanguagereference另有 1 個

clone_video

Clone a video for your brand

Remake a video you like FOR THIS BRAND from its link — a TikTok, Instagram Reel, Facebook video or reel, X post, or a direct video file URL. YouTube does not let us download videos, so a YouTube link is refused up front and nothing is charged; the user downloads it themselves and uploads the file (upload_file). Hermoso WATCHES it first (frames across the whole clip plus a transcript of the 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.

輸入參數url必填brandchangesproductlanguagedurationSeconds

click_to_ad

Plan a video ad from a product link

Paste a product page link (Shopify, Amazon or any store page) and get a creator-style video ad planned for that product. Hermoso reads the page (name, photos, what it says about the product, price), saves the product to the brand's catalog, checks the page's real product photo and pins it to this ad (the brand's saved default photo is not changed), then plans the ad. A store that hides its page from servers is read in a real browser; failing that the name comes from the link and the photo from a search, and the reply says so. Nothing renders here: show the user the concept, then call render_ad with the returned creative (dryRun:true first for the exact price). Works with or without a saved brand.

輸入參數sayurl必填stylelanguagedurationSeconds

generate_image

Generate ad image

Use this when the user asks for an image ad, ad creative, product photo or social post graphic for their business or product, or an ANIMATED GIF (format gif) or SVG file (format svg). It renders a finished image on a real image model, on their saved brand (product photos, logo), and returns a URL ready to post. For a video, use render_ad. The saved brand’s product photo is used automatically (see get_brand / list_product_photos), so never ask the user to upload it again. 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. GIF / SVG run on gpt-image-2.6 / 2.6-flare from the prompt alone (no refImages); frames + frameMs shape a GIF. 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.

輸入參數jobrawmaskmodelformatframespromptframeMsfixLabeluseBrandimageSizerefImages另有 3 個

edit_image

Edit an image

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 FILE is put on when the instruction asks for the logo (never redrawn by the model): an ask only about the logo ('the logo is wrong', 'make the logo bigger'; or logo:true, any language) runs no image model. 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.

輸入參數logomaskimage必填modeldryRunmarkupremovalfixLabelcomplexityinstruction必填

headline_variants

Headline variants of a static ad

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.

輸入參數briefcountimage必填fixLabelheadlines

resize_ad

Resize a static ad for other placements

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

輸入參數image必填fixLabelaspectRatios

localize_ad

Localize a static ad into other languages

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.

輸入參數image必填fixLabellanguages必填

make_thumbnail

Make video thumbnail

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 `bakedUiT

輸入參數fontlogosplittakestopictweaklogo3dpeopleemotionbakeTextemotionsheadline另有 21 個

generate_voice

Generate voiceover or dialogue

RAW speech as an MP3: one voice reading a script (text), or a DIALOGUE of two or more people (lines), each in their own voice. Not for adding audio to a video (render_ad and generate_video voice their own spots; change_voice re-voices a finished clip). ONE VOICE: text + engine (default 'seed-audio'; also 'eleven-v4', 'minimax-speech', 'kokoro'…) + voice, a preset from that engine (hermoso_capabilities -> voice engines); a name the engine does not have is REFUSED for free with its real list, and a few engines make their own voice (the reply says which voice spoke). DIALOGUE: lines [{speaker, voice, text}] in speaking order. Each speaker keeps ONE voice, no two speakers share one, a speaker with no voice gets one nobody has; up to 10 voices and 10,000 characters, refused for free past that. Default engine: the dialogue model (Eleven v3 Dialogue, voices Aria, George, Sarah, Roger, Laura, Charlie, Callum, River, Liam, Charlotte, Alice, Matilda, Will, Jessica, Eric, Chris, Brian, Daniel, Lily, Bill, or an ElevenLabs voice id; '[laughs]'-style cues work); or name any voice engine (e.g. 'eleven-v4') to read each line on it and join them in order. dryRun:true returns the exact credits and makes nothing; the price quoted is the charge. Paid, priced by characters (hermoso_capabilities lists credits per 1,000).

輸入參數textlinesvoicedryRunengine

generate_music

Generate music

RAW music: describe it (genre, mood, instruments, tempo), get an instrumental MP3. No length: a full track at a flat fee (explainerMusicCredits in hermoso_capabilities). With seconds: by the second up to about 38 s. A named model always runs.

輸入參數modelprompt必填seconds

generate_text

Generate text

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 (billed at the writing model's token cost, by length).

輸入參數rawmodelprompt必填

render_ad

Render ad video

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

輸入參數boardmodelmusicdryRunlockupcreatorendCardinsertsscreenscaptionscreative必填ttsVoice另有 11 個

make_template_ad

Make template ad

Use this when the user asks for a quick, low-cost ad or post built from their words and pictures: a slideshow, carousel, iMessage or ChatGPT chat, Notes, kinetic type, myth vs fact, an app tour, or their own HTML design. Rendered from HTML, no AI model, ~30s, billed for its compute only, ready to post. Presets are SHORTCUTS; 'custom' is YOUR OWN design as config.html (+ css), so no layout, type, 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.

輸入參數config必填

make_motion_film

Make a motion graphics video

MOTION GRAPHICS and ANIMATED VIDEO in one call: kinetic typography (animated text), a logo animation or intro, a product launch film, an animated social cut. MOVING PICTURES are the default for launch, product and social: in each scene with a picture, one thing physically happens (a seed splits open, an eye opens, the view travels through a forest), filmed by an image-to-video model, with the type, transitions and the music's beat put on top; a few minutes, priced per shot, and with no pictures given each scene paints its own. moving:false, and always for type and logo, makes it in code with no video model: type, pictures and drawn graphics filmed by a moving virtual camera, a 1080p MP4 in about a minute, billed for its compute only. Pick it for 'animated video', 'motion graphics', 'kinetic text', 'text animation', 'logo animation', 'logo intro', 'animated explainer' asks. recipe: 'type' (every line its own animated card, no picture needed), 'logo' (the logo assembles out of dots and settles, the name and tagline land under it), 'launch' (30s 16:9), 'product' (15s), 'social' (15s 9:16). Or `spec`, the whole film as JSON (help:true returns the language). STEPS: without confirm it is a FREE quote that gives both prices (length, the music's tempo; the spec in structuredContent): say both; preview:true is a 1-credit contact sheet of the still version; confirm:true renders. You may write `moves` yourself, or the film's own planner writes them (that planner call is priced in the quote). Only the user's or the brand's words appear: never invent a claim, number or review. projectUrl = a past film's url returns its saved spec to edit and render again.

輸入參數helphooklogomoodnamesizespecbrieflinesmodelmovesmusic另有 11 個

finish_video

Finish video

Post-process an EXISTING rendered video (its served mp4 URL) with the proven direct-response 'reviewer' finish and/or a film-grain pass — no AI model, ~30s, billed for its compute only. pills=true composites a header pill (e.g. '10/10 would buy again'), a brand-accent sub-pill, and 3-4 green-check proof pills cascading in on the beat (YOU author the copy: header ≤40 chars, sub ≤34, each point ≤44 — 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.

輸入參數subgrainpillsaccentheaderpointsvideoUrl必填

post_edit

Post-production edit

MECHANICAL post-production on an EXISTING video (URL): ordered primitives run by ffmpeg, ~2 credits flat, NO AI model, as a NEW video. Ops: a branded end card, trim, speed (0.5-2x), mute (whole or a window), audio_gain (-20..+6 dB), fade_out, music (a bed under the clip, ducked: omit track = a free library track picked by mood, no attribution; or track = an audio link: an upload_file URL, a find_sound or generate_sfx 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, or at:{word} and until:{word} to time them to a spoken phrase), join (this video FOLLOWED BY clips[]: Library URLs, direct files or public post links, as one 1080x1920 video). Presets are shortcuts: x/y, grain amount, join transition and bridge sound are free parameters. '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 (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. 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. Feedback maps to one named move, never a re-render: quieter/louder (music db, audio_gain), faster/slower (speed; slower = zooms and pans at 0.7x), tighten (trim), retime (to a word anchor), recut (order, in-out points), regrade (timel

輸入參數ops必填accentdomaindryRunvideoUrl必填brandName

fix_beat

Fix a video beat

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. A FINISHED VIDEO-BLOCKS FILM (make_explainer's explainer or faceless channel; get_job says it is editable) is edited ONE BLOCK at a time instead: pass `job` (its job id) and `block` (1-based) or `atSeconds`, with `prompt` for a different shot and/or `line` for new narration words. Only that block is re-rendered and/or re-read; every other block, take, the music and the captions are the film's own and stay aligned. It is priced as that block alone and charged exactly what it quotes (dryRun). The original film is kept; a new film comes back, and it can be edited again. Feedback maps to one named move, never a re-render: quieter/louder (music db, audio_gain), faster/slower (speed; slower = zooms and pans at 0.7x), tighten (trim), retime (to a word anchor), recut (order, in-out points), regrade (timeline exposure/contrast/saturation keys).

輸入參數joblineblockdryRunpromptrefImagevideoUrlatSecondsendSecondsstartSecondsspeechWindows

clip_video

Clip a long video

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 Vimeo / Loom / Dailymotion / Streamable / Rumble / Wistia / Twitch / TED watch link (NOT YouTube: YouTube does not let us download videos, so for a YouTube video the user downloads it and uploads the file), 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 (per-sentence, not word-level); never promise frame-accurate sync. captionsBurned counts the clips that really carry a burned track and captionNote says why any are bare.

輸入參數countvideo必填captionsaspectRatio

add_subtitles

Add subtitles to a video

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

輸入參數autoburncuesvideo必填textStylewordsPerCue

make_explainer

Make an explainer video

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, mostly one-detail edits of the frame before), narrates each section with TTS, holds each picture PERFECTLY STILL for its own slice of the narration (motion is the CUT RATE), then composites the end card (and any text you asked for). BURNED ON-SCREEN TEXT IS OFF BY DEFAULT; the film ships clean unless the user asks; `captions:true` adds held key points and `subtitles:true` adds subtitles timed to the narration. `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 renders ONE style-key image and locks every scene to it. 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 spend. Takes SEVERAL minutes — one image render per frame; NOT for a short product ad: use render_ad, generate_video or make_template_

輸入參數lanemusicstyletopicvoicedryRunformatscriptcameraschannelcreatorendCard另有 13 個

resume_job

Resume a stopped film

Finish a video-blocks explainer or faceless-channel film (make_explainer) that STOPPED partway: a block the video model would not render, or a server restart. What it already made is kept (the script, the style key, the narration and every block that rendered), so this renders ONLY the missing blocks and re-assembles the same film, with the same topic, look, voice, length and options. It reserves credits only for what is missing and never charges again for what was kept. get_job and list_jobs show which jobs can be resumed and which blocks are kept. Quote it with dryRun first and tell the user the number. A finished film is not resumed: to redo one block of a finished film, use fix_beat with its job id.

輸入參數jobId必填dryRun

make_series

Make a series of faceless short videos (Content Factory)

CONTENT FACTORY: turn ONE topic into a SERIES of faceless short videos (narration over pictures, no presenter), optionally in the STYLE of a trending reference video. Give `topic` ("make a video about…"), `episodes` (1-10, default 1) or your own `episodeTitles`, `durationSeconds` (20-120 per episode, default 30) and optionally `reference` (a TikTok / Reel / Facebook / X link or a video file URL whose look, pacing and opening hook every episode copies, with plain subtitles only if the reference burns its own; never its words or characters). Find a trending reference with search_tiktok / search_instagram / search_youtube when the user wants one. THREE STEPS, ALWAYS IN THIS ORDER: (1) call with no `confirm` for a FREE quote of the whole series (per episode and total); (2) optionally `planOnly:true` to see the episode titles and the style card first (a small planning charge); (3) `confirm:true` (pass the `plan` from step 2 to reuse it) queues every episode and returns their job ids; poll get_job. To make MORE of a series later, pass its returned plan as `moreOf`: same style, no episode repeated, the reference not watched again. Each finished episode can be scheduled with this tool again: pass `jobIds` plus `schedule` {channels, at, everyHours, timezone} and it books one post per finished episode, spaced `everyHours` apart (default 24). Captions follow the reference (on if it burns words on screen) unless `captions` says otherwise.

輸入參數planstyletopicvoiceformatjobIdsmoreOfconfirmcaptionsepisodesplanOnlyschedule另有 5 個

make_effect

Run a one-click effect on a photo

ONE-CLICK EFFECTS, the same ones as the /tools pages: a photo goes in, a styled picture comes out, and most can then become a 5-second clip. Plush toy, 3D figure, mascot, comic book, pixel game, billboard ad, giant product, bullet time, packshot, outfit swap, headshot, relight and many more. Name no `effect` for the free list. `effect` alone: what it takes. `effect` + `images`: a free quote; add confirm:true to make the picture. Then `effect` + `still` (that picture's URL): a free quote for the clip; confirm:true makes it. `describe` adds anything of your own. For a look not on the list use generate_image. A real person's photo follows the same likeness rules as every render.

輸入參數textstilleffectimagesspeechconfirmoptionsdescribeaspectRatio

product_sizzle

Product sizzle (music-led)

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

輸入參數ctaspecsstyleprompt必填secondsrefImagebrandNamemusicMoodresolutionaspectRatio

generate_video

Generate video

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. A GIF is generate_image format gif. 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).

輸入參數rawloopaudiomodelshotsspeakanglesdryRunextendpromptcreatorendImage另有 15 個

generate_avatar

Generate talking avatar

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.

輸入參數seedimage必填voicedryRunenginepromptscript必填resolutionacceptQueue

stitch_video

Stitch multi-scene video

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

輸入參數modelvoicescenes必填voiceoverresolutionaspectRatiodurationSeconds

get_job

Get render job

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

輸入參數id必填

plan_week

Plan a week of posts

Use this when the user wants a week of social posts planned, or answers yes or no to planning their week. It drafts one batch of 5 posts for the active brand: a new image, a caption and a suggested day and time for each. Without confirm it returns the price and whether the batch fits the account’s posting allowance, and spends nothing. confirm:true starts the batch and bills credits; read the posts with get_job. The posts stay drafts until the user approves them with run_post_refill, and this tool publishes nothing. decline:true records that the user said no.

輸入參數confirmdeclinetimezone

list_skills

List skills

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.

無需輸入參數。

get_skill

Get skill

Load a bundled skill’s full SKILL.md workflow instructions by name (from list_skills), or a built-in in-app skill by its id (e.g. youtube_script, retention_skeleton). Follow the loaded instructions to run that workflow with the other tools. Read-only, free.

輸入參數name必填

save_skill

Save a skill

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

輸入參數name必填branddirective必填

delete_skill

Delete a custom skill

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.

輸入參數id必填brand

list_memory

List memory

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.

輸入參數limitcategory

remember

Remember a fact

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

輸入參數text必填brandcategory

forget

Forget a memory

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.

輸入參數id必填brand

tidy_memory

Tidy the Memory list

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

輸入參數brandconfirm

list_swipefile

List the swipefile

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.

輸入參數limitcollection

save_to_swipefile

Save ads to the swipefile

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

輸入參數branditems必填collection必填

update_saved_creator

Update a saved creator (outreach status, note)

Set the OUTREACH STATUS and/or a NOTE on a creator already saved in the swipefile (find_creators -> save_to_swipefile, or the heart on a creator card). Status is one of new | contacted | replied | booked | passed. The note is free text (deal terms, rate, what was sent). Reads back the updated row. Use list_swipefile to find the key. Free.

輸入參數key必填notebrandstatus

list_playbooks

List playbooks

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.

輸入參數fulllimit

save_playbook

Save a playbook

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

輸入參數name必填brandhooksplaysanglessourceformats

delete_playbook

Delete a playbook

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.

輸入參數id必填brand

list_creators

List saved creators

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.

輸入參數limitgender

save_creator

Save a creator

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

輸入參數lookname必填brandimageposesvoicesourceuseAnyway

delete_creator

Delete a creator

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.

輸入參數id必填brand

update_brand

Update profile fields

Patch SPECIFIC fields of the active profile (name, domain, sells, summary, category, audience, positioning, voice, style, goal, logos, brand/product name pronunciations, profileType: explore = kept but unused) WITHOUT overwriting the rest — a read-modify-write on the saved brand. Use for “change our voice to playful”, “we sell to dentists now”. To onboard a brand from scratch, use draft_brand. If no brand is saved yet and you only INFERRED one from what the user is making, ask them to confirm it is their brand before saving it (they may be working for a client or just trying things). Only pass the fields you’re changing.

輸入參數goallogonamebrandsellsstylevoicedomainsummaryaudiencecategorylogoDark另有 5 個

store_get

Read a workspace store

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

輸入參數key必填limit

get_settings

Read app settings

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

無需輸入參數。

update_settings

Change app settings

Change this account's app settings. language = the language EVERY ad, script and plan is written in from now on (chat replies follow the language the user writes in) (say the language in plain English, e.g. "German", "Japanese", "Brazilian Portuguese") — it applies to renders made over MCP as well as in the app. theme = the app's appearance, "dark" or "light". watchEmail = the weekly competitor-watch email on/off. Only pass what you are changing. Account-wide (every profile), and it takes effect on the next call.

輸入參數themelanguagewatchEmail

list_connectors

List connectors

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

無需輸入參數。

list_connector_accounts

List a connector’s accounts

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

輸入參數provider必填

set_connector_accounts

Choose which accounts a brand may use

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

輸入參數provider必填accountIds必填

disconnect_connector

Disconnect a connected account

Disconnect a third-party account from this workspace (Meta, Google Ads, Google Drive/Sheets/Docs, YouTube, TikTok, LinkedIn, X, Reddit Ads, Pinterest, Google Business, Microsoft Advertising/OneDrive, Slack, …). This always drops the stored credentials, so every tool for that provider stops working immediately and posts/campaigns already published are NOT affected. IT NEVER REVOKES THE GRANT AT THE 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.

輸入參數accountconfirmprovider必填

connect_connector

Connect a paste-a-key account

Connect an account that links with a KEY rather than a sign-in screen, from here, with no browser: AppLovin Ads, Stripe, Intercom, Mailchimp, Klaviyo, Vercel, ChatGPT Ads, Apple Ads, Bluesky, Telegram, Bing Webmaster Tools, PostHog, Mixpanel, Amplitude, WordPress, Slack, Discord, Webhook. Pass provider and fields in that provider's own field names (listed below, * = required). The key is checked 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} · mailchimp {apiKey*, server} · klaviyo {apiKey*} · 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} · wordpress {siteUrl*, username*, appPassword*} · slack {webhookUrl, announceWebhookUrl} · discord {webhookUrl, announceWebhookUrl} · webhook {webhookUrl*}.

輸入參數fieldsprovider必填

leave_connector

Remove my own account from a shared connection

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.

輸入參數provider必填

list_team

List team members

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

無需輸入參數。

invite_member

Invite a teammate

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.

輸入參數roleemail必填confirm

remove_member

Remove a teammate

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

輸入參數email必填confirm

set_role

Change a teammate’s role

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

輸入參數role必填email必填confirm

list_jobs

List render jobs

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

無需輸入參數。

list_errors

List errors users hit

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.

輸入參數kindlimitsincesurface

error_detail

Error detail

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.

輸入參數fingerprint必填

find_competitors

Find competitors

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

輸入參數modedomain必填

pull_competitor_ads

Pull competitor ads

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

輸入參數urlnamesortbrandlimitdomaincompanycountrywebsitecompanyName

set_competitor_watch

Set the market radar

Set, change or stop a brand's MARKET RADAR (the standing competitor watch; Ad Spy > Watching in the web app; each brand has its own), run weekly or daily: newAds (what watched brands launched), winners (their ad passing 30 days running), trends + topPosts (formats and most-viewed posts for niche topics), ideas (3 to make). Only what you pass changes. competitors / topics REPLACE their lists (max 8 brands, 5 topics); both empty stops it and clears findings. auto:true picks 3-5 competitors, 2-3 similar brands and 2-3 topics from the saved brand or `seed` (one model call, billed at its token cost). dryRun:true quotes one check, saves nothing. Saving is free; each check holds its quote first and is skipped when the balance is short. runNow is refused while a check runs or just after one finished. Read it with list_watch_findings.

輸入參數autoseedbrandemailforcedryRunrunNowtopicscadencemodulescompetitors

list_watch_findings

Read the market radar

Read what a brand's MARKET RADAR (set_competitor_watch) has found: each watched brand's new ads, the winners (ads past 30 days running, with a ready brief to make your own version), the trending formats and top posts for its topics, and the ideas to make, plus its state (lastRun, nextRunAt, lastRunOk, paused) and the quote for its next check. Findings marked `seed:true` are what a brand was already running at its first check, not new launches. Read-only and free; it never runs a check.

輸入參數brandlimitcompetitor

research_ads

Research ads

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

輸入參數brandquery必填

search_meta_ads

Search Meta ads

Structured Meta (Facebook/Instagram) Ad Library pull — use when you know exactly WHAT to fetch: a keyword (query) OR one advertiser (companyName / pageId). Returns compact JSON {page_name, body, cta, link, dates, media} per ad. For open-ended research that needs judgment across platforms, use research_ads instead. Spends credits on each ad-library read it makes: one for a keyword; for a brand name, the page lookup and its ads (plus a keyword search when that page has none).

輸入參數limitquerydomainpageIdstatuscountrymediaTypecompanyName

search_google_ads

Search Google ads

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

輸入參數limitdomainregionadvertiserId

search_linkedin_ads

Search LinkedIn ads

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

輸入參數limitcompanykeywordcompanyIdcountries

find_creators

Find the creators already winning in a niche

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.

輸入參數limitniche必填enrichqueriesplatformsmarketplaceminAvgViewsminEngagement

search_posts

Top posts about any topic, brand or product

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

輸入參數limittopic必填queriesplatforms

search_tiktok

Search TikTok

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

輸入參數limitquery必填

search_instagram

Search Instagram

Organic Instagram REELS keyword search (/v2/instagram/reels/search — our only IG keyword surface; profile/hashtag pulls go through fetch_social_data with a handle). Returns compact JSON {desc, author, handle, plays, likes, link, cover} per reel, ranked by plays. It matches captions worldwide and cannot be limited to a country or language: for one market, search in the words people there use, or read named local accounts' Reels through fetch_social_data's Instagram profile endpoint, which is the better first step for a local market. Spends about a credit.

輸入參數limitquery必填

search_youtube

Search YouTube

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

輸入參數limitquery必填

search_reddit

Search Reddit

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

輸入參數limitquery必填

search_threads

Search Threads

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

輸入參數limitquery必填

search_meta_places

Find a place to tag on Instagram or Facebook

Find a PLACE to tag on an Instagram or Facebook post: a shop, cafe, restaurant, venue or landmark. Give its name with the city ("Blue Bottle Coffee Oakland"), or paste its Facebook Page link, an Instagram place link (instagram.com/explore/locations/...) or its numeric id. A pasted link or id is read and comes back as that Page's taggable pageId with its name and address, or a plain reason it cannot be tagged. Returns a handful of matching Facebook Pages that have an address: pageId, name, address, category, link. Pass the chosen pageId as locationId (Instagram) or place (Facebook) on post_to_meta, or instagramLocationId on schedule_post. These are matches to CONFIRM with the user (check the name and address; a place tag is public), and Instagram checks the place when the post is made. Needs no connected account. Spends about 1 credit per Page it checks (a name search checks up to 5 Pages, so about 5 to 7 credits); a pasted Facebook Page link or numeric id is about 1 credit; an Instagram place link costs nothing.

輸入參數nearlimitquery必填

fetch_social_data

Fetch social data

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.

輸入參數path必填params

get_brand

Get saved profile

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.

輸入參數brand

draft_brand

Draft profile

Fill in a profile (a business brand or a creator) from a website domain, a free-text description, or a social handle, into a {name, products, logo, …} object you can pass to plan_ad / generate. IMPORTANT: a domain can resolve to a DIFFERENT company than intended (e.g. bala.com is an engineering firm, not the Bala fitness brand at shopbala.com). Before spending any credits on research or renders, 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.

輸入參數savedomainplatformconfirmeddescriptionsocialHandle

list_library

List library

Browse this workspace's Library — every image/video generated in the Studio, and uploads (source:'upload'), newest first (the same Library the web app shows). Returns served URLs you can open directly or hand to fetch_asset for a download link, plus each asset's id (L0, L1…, which plan_ad and render_ad `inserts` take), a human TITLE (the ad's headline or concept, the prompt's first words, or the upload's name), kind, model, and age. When the user names an ad by its headline ("the Afternoon fix ad"), pass `query` to find it by title. Free, read-only.

輸入參數kindlimitqueryuploads

delete_library_item

Delete from Library

PERMANENTLY delete images/videos from this brand's Library: the entry and the stored file. Pass ids (L0… from list_library) or urls. Without confirm it lists what would be deleted; ask the user, then call again with confirm:true. Free.

輸入參數idsurlsconfirm

fetch_asset

Fetch asset

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

輸入參數url必填name

analyze_video

Analyze video

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.

輸入參數url必填wordsframesanchors

find_sound

Find a sound

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

輸入參數urlpickquery

make_insert

Make an insert clip

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.

輸入參數imagesound必填videosecondssoundEndsoundStartvideoStart

score_ad

Score ad

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

輸入參數url必填kindintent

check_video

Check a video

Check a finished video for named, mechanical issues, ONLY when the user asks (never after every render, and it never re-renders): loudness vs a target (default -14 LUFS) and true peak, black and frozen stretches, captions or text outside the platform-safe band, text running off the frame. Optional assertions about named elements: appearsBy {element, bySec}, before {a, b}, staysInFrame {element}, keepsMoving {maxStaticSec}. Each issue comes with a Fix line. The loudness, black and frozen checks are free; the on-screen text and element read is a small paid frame read (frames:false skips it).

輸入參數url必填framesassertionsloudnessTarget

reframe_video

Reframe video

Reframe a video to a different aspect ratio (e.g. 16:9 master -> 9:16 vertical): the whole original frame is kept and the new canvas around it is generated, with the sound and timing unchanged. Clips up to 300 seconds. Paid render; returns the served URL of the reframed video. Every placement at once (a free blur-pad by default): repurpose_video. A presenter on any video: add_presenter.

輸入參數video必填aspectRatio必填

repurpose_video

Repurpose a video for every placement

ONE video in, EVERY placement out, in one job, as a named file list (<name>_<placement>_<WxH>[_<lang>].mp4) with a manifest. Placements: 'everywhere' (default: 9:16, 4:5, 1:1, 16:9), any W:H, a pixel size (1200x628) or a platform word (tiktok, reels, stories, feed, square, youtube, linkedin, pinterest). Each is reframed by a BLUR-PAD by default (the whole frame over a blurred copy of itself; free, no AI), reframe 'crop' (fill, edges cut; free) or 'ai' (the new canvas generated around the frame; paid, for a canvas the engine makes, else padded and said). A placement may be {placement, reframe} to mix. Optional captions (word-timed, ~4 words a cue, placed for each canvas) and languages (a lip-synced dub per language; every placement of each). A pack of pads and crops only is billed 0. dryRun:true = the exact price (the sum of every leg's own price), which is what it charges. For ONE canvas use post_edit op pad.

輸入參數padnamevideo必填dryRunreframecaptionslanguagesplacementswordsPerCue

insert_scene

Insert a new scene into a video

INSERT a newly generated scene into a clip you already have: the video plays up to startTime, the new scene (5 to 13 seconds) plays, then the original picks up again at resumeTime. Use for a product close-up, a reaction, a reveal or a missing beat in the middle of an ad without re-rendering the rest. resumeTime = startTime keeps every second of the original; a later resumeTime also cuts the footage in between. Describe the scene in prompt and/or attach up to 4 reference pictures (refImages) or 1 reference clip (refVideos). Clips up to 60 seconds; both times between 1.625 and 60 seconds. Paid render (billed per second of NEW scene); returns the whole video's URL.

輸入參數video必填promptsecondsrefImagesrefVideosstartTime必填resolutionresumeTime必填

upscale_video

Upscale video

Upscale a video to higher resolution (2x) for final delivery. Paid render; returns the served URL. A 480p Seedance 2.5 draft: finish_draft (same take, native 1080p). Three engines: the default ('standard') is the safe precision upscaler; engine:'flux' is the FLUX 3 video upscaler (1080p/2K/4K) with an optional mode:'creative' detail-enhancement pass; engine:'detail' rebuilds fine detail such as hair, skin and fabric, the sharpest result for a hero shot and the most expensive. Pick 'flux' or 'detail' when the user asks for added detail rather than a faithful enlargement.

輸入參數modevideo必填engine

restyle_video

Restyle a video

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

輸入參數stylevideo必填enginedescribefaceRoutecharactersresolutionstyleImages

edit_video

Edit a video clip

SWAP AN OBJECT IN A VIDEO ('replace the cup with our can' + the product photo as elements) or EDIT an existing clip from a plain instruction (video-to-video): the motion, timing, framing and cut stay, the named thing changes. 'change only the mug to red', 'make it nighttime', 'restyle it as claymation'. Your words are wrapped so the model keeps everything else identical, changes only what you 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), translating what is SPOKEN (dub_video) or a saved creator's face (recast_motion). Translating or rewriting the on-screen words of a video laid out from a design (a template ad, a custom design) is textChanges with every line at once: one re-make of the design, about 2 credits. Best on 3-15s clips.

輸入參數video必填engineliteralelementslightingfaceRoutekeepAudiopreviewAtreferenceinstruction必填textChangespreviewStill另有 2 個

multiply_ad

Multiply an ad

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, and any burned-in captions are removed. Pass the source video URL (a render, 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 keeps the source's length, cut and 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). ONE JOB PER VARIANT: get_job each until done; never describe a variant before its URL arrives. COST: quoteOnly:true returns the exact credits free: state that first. A paid planning read (planCredits) runs next, then each variant bills perVariantCredits. dryRun:true pays for that read; pass its `plan` back to render without paying again. images takes up to 9 of the user's product, people, clothes or places, and change names them @Image1, @Image2 in order ('she holds the bottle in @Image1'); the saved brand's product photo is read as a reference to its real look (useBrand:false leaves it out). A TikTok, Reel, Facebook or X post link works as the source (not YouTube: upload the file): one paid lookup reads it into a file. quality: 1080p (default) or 720p; quoteOnly prices both. Regions: pass regions:['Berlin','Tokyo'] to restyle variants per market; translation is separate: 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

輸入參數axesplancountnotesvideo必填changedryRunimagesqualityregionsuseBrandquoteOnly

hook_variants

New opening hooks for a video

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: quoteOnly:true returns the exact credits free: state that first. A paid planning read (planCredits) runs next, then each planned version bills like fix_beat for the hook's seconds. dryRun:true pays for that read; pass its `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).

輸入參數plancounthooksnotesvideo必填dryRunuseBrandquoteOnlyresolutionhookSecondsproductImage

dub_video

Dub video

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.

輸入參數video必填voicedryRunscriptlanguage必填

change_voice

Change narrator voice

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.

輸入參數video必填voice

recast_motion

Recast motion

Copy the motion of any video (motion transfer): re-perform a clip's motion, camera and timing with YOUR character, product, clothes or place; sound kept. image (+ images, @Image1, @Image2… in prompt) = who and what. engine auto (default): a person clip + ONE photo of a person (saved, real or AI), 4-30s -> seedance (Seedance 2.5 through our face library, 720p: the clip's scene, beats and sound kept, the WHOLE person swapped) when the clip is one of our renders or faceRoute is set; otherwise wan (Wan 3.0, scene kept, 3-15s, 720p), past 15s kling-edit (Kling 4.0 Edit, 3-30s, 1080p by asking); several photos (one per person) -> h3 (H3 Max Recast, ≤4 photos, 5-30s); no person in the clip -> seedance. By name: kling-edit; wan = Wan 3.0 (≤15s, keeps the set); wan-prime; h3; kling = Kling Motion Control (ONE picture, its background becomes the set). voice/language: the new performer gets a new voice (same timing) or speaks another language (lip-synced), in the same job and price. Billed per output second, ~5 min per 5s; dryRun quotes.

輸入參數tierimagevideo必填voicedryRunengineimagespromptlanguagefaceRouteresolutionorientation

plan_variations

Plan ad variations

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.

輸入參數brandcountproduct必填language

competitor_teardown

Competitor teardown

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

輸入參數adslanguagecompetitor必填

check_ad_policy

Check ad policy

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.

輸入參數copy必填claimscategoryimageDescription

clone_static

Clone a static ad

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.

輸入參數brandIdimageUrl必填

remix_static

Clone a static ad (old name)

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

輸入參數brandIdimageUrl必填

mine_angles

Mine customer angles

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 credits on the reads and the model's token cost.

輸入參數brandIdreviewsreviewsUrluseOwnReviewsOnly

list_product_photos

List product photos

List the product photos ALREADY saved in your workspace — the brand's product library plus any app-store screens (also surfaces photos locked in your OTHER creations, since a set product lands in the shared library). FREE — returns each photo's url + the product it shows (the shop's own title when one is on file). These are already saved: never ask the user to upload them again. Call it before set_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).

輸入參數brandId

set_product_image

Set product photo

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

輸入參數brandIdimageUrl必填source_note

fetch_app_screens

Fetch App Store screens

Pull an APP brand's REAL store screenshots (Google Play or App Store) into the workspace brand, so a screen-hungry native format can use them. Use when the brand has 0–1 app screens on file and you want make_template_ad(template:'app-ui-tour'), or the user asks to 'pull my app's screenshots'. A store link or package id goes in storeUrl and is read exactly; else pass appName (defaults to the saved brand's name). A web app with no store listing gets the screenshots of the product its OWN website shows (pictures the site labels as its interface). Call it before telling the user you have no screenshots of their app. FREE — a keyless store lookup or one read of the brand's homepage. 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.

輸入參數appNamebrandIdstoreUrl

list_meta_posts

List the Page’s / Instagram account’s own posts

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.

輸入參數brandlimitcursorpageIdtaggedtargetaccountincludeUnpublished

list_published_posts

List what this brand has published

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.

輸入參數brandlimitcursorchannel

版本歷史

73
  1. v0.1.529最新Nov 8, 2026
  2. v0.1.528Nov 7, 2026
  3. v0.1.524Nov 5, 2026
  4. v0.1.523Nov 5, 2026
  5. v0.1.518Nov 5, 2026
  6. v0.1.514Nov 4, 2026
  7. v0.1.505Nov 4, 2026
  8. v0.1.500Nov 4, 2026
  9. v0.1.498Nov 3, 2026
  10. v0.1.489Nov 1, 2026
  11. v0.1.488Nov 1, 2026
  12. v0.1.487Nov 1, 2026
  13. v0.1.486Nov 1, 2026
  14. v0.1.485Oct 31, 2026
  15. v0.1.484Oct 31, 2026
  16. v0.1.478Oct 30, 2026
  17. v0.1.471Oct 30, 2026
  18. v0.1.470Oct 30, 2026
  19. v0.1.467Oct 30, 2026
  20. v0.1.466Oct 29, 2026
  21. v0.1.453Oct 26, 2026
  22. v0.1.448Oct 26, 2026
  23. v0.1.440Oct 26, 2026
  24. v0.1.436Oct 22, 2026
  25. v0.1.435Oct 20, 2026
  26. v0.1.434Oct 20, 2026
  27. v0.1.432Oct 19, 2026
  28. v0.1.426Oct 19, 2026
  29. v0.1.414Oct 19, 2026
  30. v0.1.410Oct 16, 2026
  31. v0.1.409Oct 16, 2026
  32. v0.1.408Oct 15, 2026
  33. v0.1.407Oct 12, 2026
  34. v0.1.406Oct 8, 2026
  35. v0.1.405Oct 7, 2026
  36. v0.1.404Oct 7, 2026
  37. v0.1.403Oct 7, 2026
  38. v0.1.397Oct 6, 2026
  39. v0.1.385Oct 6, 2026
  40. v0.1.381Oct 6, 2026
  41. v0.1.377Oct 5, 2026
  42. v0.1.374Oct 5, 2026
  43. v0.1.373Oct 5, 2026
  44. v0.1.372Oct 2, 2026
  45. v0.1.371Oct 1, 2026
  46. v0.1.366Sep 30, 2026
  47. v0.1.362Sep 30, 2026
  48. v0.1.359Sep 30, 2026
  49. v0.1.355Sep 30, 2026
  50. v0.1.347Sep 29, 2026
  51. v0.1.345Sep 29, 2026
  52. v0.1.336Sep 28, 2026
  53. v0.1.330Sep 28, 2026
  54. v0.1.320Sep 26, 2026
  55. v0.1.310Sep 25, 2026
  56. v0.1.303Sep 25, 2026
  57. v0.1.294Sep 25, 2026
  58. v0.1.285Sep 24, 2026
  59. v0.1.281Sep 24, 2026
  60. v0.1.280Sep 24, 2026
  61. v0.1.275Sep 23, 2026
  62. v0.1.273Sep 23, 2026
  63. v0.1.272Sep 23, 2026
  64. v0.1.270Sep 22, 2026
  65. v0.1.269Sep 22, 2026
  66. v0.1.265Sep 22, 2026
  67. v0.1.264Sep 21, 2026
  68. v0.1.263Sep 21, 2026
  69. v0.1.258Sep 21, 2026
  70. v0.1.256Sep 18, 2026
  71. v0.1.252Sep 18, 2026
  72. v0.1.251Sep 17, 2026
  73. v0.1.249Sep 16, 2026

評分

已滿足適用檢查項的 100%

該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。

  • 近 30 天內可連通必需25 分
  • 已宣告認證方式必需10 分
  • 已列出工具15 分
  • 描述必需15 分
  • 圖示10 分
  • 文件15 分
  • 授權條款10 分