hermosodocs

Creative generation

Plan, render, and finish image and video ads. Long renders run as jobs you poll.

67 tools. Every name, description and parameter on this page is generated from the running MCP server — see how this reference is built.

Voice and video editing at a glance

What each tool lets you set. The full parameters are on each tool further down.

Voice

  • generate_voice: text to speech. You set the script (up to 900 characters), the voice model (engine) and the voice (voice, one of that model's named presets, listed in hermoso_capabilities). It returns an MP3. This tool has no speed, pitch, volume or emotion parameter.
  • Emotion and delivery. In a video ad from plan_ad and render_ad, every spoken line has its own voice_dir, a delivery direction in plain words ("voice low and warm", "bright and breathless", "confident, clean tagline read"), and a line can carry bracketed performance cues such as [small laugh], [exhale] or [beat], which are performed rather than spoken. Voices default to a neutral accent; a line's direction can name another.
  • Volume. post_edit has audio_gain (-20 to +6 dB), mute (the whole clip or a window), fade_out, music (a bed under the clip with its own db level, dipping under speech by default) and voiceover (words spoken over the clip, its own sound kept or replaced). edit_timeline takes a per-segment audio.gain in dB, as a constant or keyframes, plus fades and J- and L-cuts.
  • Pitch. No tool takes a pitch value. Pitch follows the voice you pick.
  • Re-voicing. change_voice swaps the narrator of a finished video for another voice and keeps the timing, lip-sync and background sound. dub_video translates the speech into another language, lip-synced, with the background kept.

Clip, arrange and finish

  • clip_video: one long video into 1 to 8 ranked short clips, each its own MP4, in 9:16 (default), 1:1, 16:9, any W:H or keep, with subtitles burned in unless captions is false.
  • post_edit: trim, speed (0.5 to 2x), mute, audio_gain, fade_out, music, voiceover, text, watermark, grain, append_card (an end card) and join (clips appended in order, with a cut, a crossfade or any ffmpeg xfade transition), applied in the order you list them.
  • edit_timeline: up to 12 segments on an output timeline, each with its own in and out points and keyframed scale, position, rotation, opacity, blur, speed ramps, freeze frames, reverse and sound; HTML overlays and masks; a canvas of 9:16, 1:1, 4:5, 16:9, any W:H or exact pixels, at 24 to 60 fps.
  • stitch_video: a multi-scene render longer than one clip of the model. insert_scene: a new 5 to 13 second scene inside a clip you already have. fix_beat: re-render one 1.5 to 8 second window, video track only, with all of the audio kept.
  • add_subtitles: burns subtitles into any video and returns the .srt, timed per sentence or per word, or from your own cues; textStyle sets the look and burn: false returns only the .srt.
  • reframe_video: to 9:16, 1:1, 16:9, 4:3, 3:4, 21:9 or 9:21, keeping the whole original frame and generating the new space around it, with sound and timing unchanged; clips up to 300 seconds.
  • upscale_video: 2x, with engine standard, flux or detail. finish_draft: a 480p draft regenerated at 1080p. finish_video: proof pills, film grain, or both.
  • edit_video changes one thing in a clip from an instruction, and restyle_video redraws every frame in a new look.

File types

Video comes back as MP4, voice and music as MP3, subtitles as SRT, and stills as PNG, JPG or WebP. Every result is a URL you can download; save_to_drive and save_to_onedrive put it in the user's Google Drive or OneDrive, and the publishing tools post or schedule it to a connected channel.

planning

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`. Uses credits for the planning step.

ParameterTypeDescription
brandstring | objectbrand name, or a brand profile object {name,domain,category,palette,products,…}. OMIT to use the workspace’s SAVED brand + memory automatically (see get_brand); use draft_brand to onboard a new one
productrequiredstringwhat to advertise + any angle/offer the user specified
format"auto" | "image" | "video"'image', 'video', or 'auto' when unspecified
durationSecondsnumberFor video: the total length the user asked for, in seconds (30 for "a 30 second ad"). The storyboard’s scenes add up to it and the script is sized for it. Supported range 4 to 180 seconds; a value outside the range is clamped and the reply says so. A length that fits one clip of the render model renders as one continuous clip; a longer one is joined from clips filled to that model’s clip maximum, remainder last (on a 15s-clip model, 40 becomes 15+15+10 and 17 becomes 13+4), and is never sped up. The clip maximum depends on the model: 15s on most, 30s on the longest-clip model, so a 30s ad can be one take. Leave it out when the user named no length; the planner then picks one that suits the format.
hookstringforce the VISUAL scroll-stop mechanic the opening beat is built on — a hook id from list_hooks (e.g. "direct_callout", "mid_problem", "macro_asmr"). Omit to let the planner pick. A hook that cannot be delivered in this brief is DROPPED with the reason rather than rendered wrongly — an on-screen-text hook on an authentic/UGC ad is the one that bites, because that register carries zero on-screen text.
settingstringforce the WHERE — a setting id from list_hooks (e.g. "kitchen", "gym", or a surreal one like "volcano_rim" / "airplane_wing", which are played 100% straight and never acknowledged). Omit for a neutral setting.
recipestringa recipe id from hermoso_capabilities to force an archetype
referencestringa reference to clone: an ad-library link (Facebook Ad Library, LinkedIn Ad Library, Google Ads Transparency — its real copy/advertiser are fetched) OR a VIDEO link — a TikTok, Instagram Reel, Facebook video, X post, YouTube Short/video or a direct video file — which is WATCHED first (frames + voiceover/on-screen-text transcript) so the concept keeps its hook, structure and pacing. To remake one video for this brand at its own length, clone_video is the direct tool
languagestringoutput language for the ad copy (e.g. Spanish) — default English
talent"auto" | "creator" | "product_only"who is on camera; omit to follow the format. Say 'someone new' in product to skip reusing a saved creator.
conceptsstring[]several variants: one line per concept (a different angle each); built exactly
draftobjectOnly after a video refusal that offered a lighter draft: the {model, durationSeconds} that refusal named. The plan is then written to that length and priced on that model. Use only the values the refusal gave; when an account cannot cover a video, the refusal comes before planning with three options (an image, adding credits, or this draft when one fits) and the user chooses.
insertsobject[]VIDEO ONLY: the user's own SCREENS cut in at these seconds (each item's look, kind, start and end say how), the voice running underneath. How many: at most 8, about one per 2.5 s of film (6 s: 2, 10 s: 4, 15 s: 6, 20 s: 8, 30 s: 8), 1.2-2 s each, on the line that talks about it. When there are more, the most crowded are left out first, never the last (the payoff) or one marked key, and the reply names them. The plan keeps those shots free of screens and returns the pictures as creative.inserts for render_ad to lay in (about 2 credits). One that does not fit is left out and the reply says so.

CLICK TO AD

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.

ParameterTypeDescription
urlrequiredstringthe product page link, https://…
style"ugc_selfie" | "ugc_unboxing" | "ugc_demo" | "ugc_tryon" | "ugc_product" | "ugc_shopping" | "ugc_delivery" | "transformation" | "day_in_life" | "grwm" | "ugc_addiction"ugc_selfie (talking to camera, default), ugc_unboxing, ugc_demo (how-to), ugc_tryon (try-on or haul), ugc_product (product close-ups, no face), ugc_shopping (in-store find), ugc_delivery, transformation (before and after), day_in_life (at home), grwm (get ready with me), ugc_addiction (obsessed with it)
durationSecondsnumberlength in seconds, 4-180; default 15
saystringwhat the creator should say, or the angle, in the user's words
languagestringlanguage for the script and copy, default English

image

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

ParameterTypeDescription
promptstringREQUIRED on every model EXCEPT the pose rows below. the full image prompt — subject, composition, lighting, and any on-image ad text. ON A POSE MODEL (product-in-hand / try-on) THIS IS EXTRA DIRECTION AND IT IS OPTIONAL — leave it out and the pose is built for you. If you do write one, DESCRIBE THE POSE ("she holds the bottle upright in her right hand at chest height, label to camera"); do NOT phrase it as a swap ("replace the mug with the bottle"), which is REFUSED for free, because the product then comes out the size of whatever it replaced — a 30ml bottle rendered mug-sized in testing.
refImagesstring[]local file paths or URLs of product/logo references to composite in. ON THE POSE MODELS — any row hermoso_capabilities marks `needsRefs` with a `refsMax`, such as putting your product in someone’s hands or a virtual try-on — THE ORDER IS THE CONTRACT AND IT IS NOT A COMPOSITE: refImages[0] is the PERSON photo, and the rest (up to `refsMax` minus one) are the product or garment photos. Reversed, you get the product wearing the person. A 4th product is dropped and the reply says so. Using a real person’s face, likeness or voice confirms you are them or have their consent, take full, unlimited responsibility for its use and will cover any claim against Hermoso (hermoso.ai/terms). Paid plan only; recorded.
useBrandbooleandefault true: with no refImages, the server hydrates the SAVED brand’s product/logo references so the output lands on-brand; pass false for a pure prompt-only render
rawbooleanRAW MODEL ACCESS: run the caller’s prompt on the named model with no Hermoso adjustments at all — the prompt reaches the provider byte-identical (no hex-to-colour-name rewrite, no prepended fidelity preamble) and NO saved-brand product photos are attached, so the model you name is the model that renders. Use it to drive the raw catalog; leave it off for an on-brand ad. Billing, the durable Library landing and per-model validation are unchanged.
aspectRatiostringe.g. '1:1', '9:16', '16:9', '4:5'. Each model draws its own list (hermoso_capabilities prints it per model, e.g. Nano Banana 2 goes to 1:8 and 8:1); a ratio the chosen model cannot draw is refused before anything is charged
modelstringimage model id from hermoso_capabilities. A model whose `refs.mode` is "edit" there (gpt-image-2.5) takes your refImages on ITS OWN editor, up to its `refs.max`, instead of the default compositor
job"static_ad" | "product_photo" | "marketplace" | "thumbnail" | "character_sheet" | "vector"what the picture is for. With no `model` named it renders on the engine judged best for that job (hermoso_capabilities `jobEngines.image` lists them): static_ad (the default when neither `model` nor `job` is given) = a finished image ad with its headline, copy and button on the picture; product_photo = a studio, lifestyle, flat-lay or spotlight photo of the real product; marketplace = a listing card; thumbnail = a video cover (make_thumbnail is the full thumbnail pipeline); character_sheet = a multi-view sheet of one person; vector = a logo, icon or flat illustration as an editable SVG (it takes no reference pictures, so the saved brand's photos stay off it). A named `model` always wins
imageSizestringpixel-size preset for models that support it: 1K/2K, and 4K on the models hermoso_capabilities lists with a 4K imageSize price (a 4K ask on any other model is refused, free) — omit for the default
fixLabelbooleandefault true: when the saved brand's product photo rides in this render, the product's label on the finished image is READ and compared with the photo, and re-printed from the photo at close range ONLY if it came out wrong (a label that is already right costs only the check; a re-print costs more, and dryRun:true returns the up-to price). The reply says whether the label was checked, fixed or left as rendered (`labelPass`). Pass false when the user wants the packaging left exactly as generated: nothing is checked or re-printed.
logoPlacement"auto" | "overlay" | "in_scene" | "none"overlay: real logo file laid flat. in_scene: the scene leaves a blank patch on the object and the real file is printed onto it (perspective, light). Never drawn by the model. auto (default): from the prompt
maskstringMASKED EDIT — change ONE region and keep the rest: a mask (path or URL, any size) for refImages[0]. TRANSPARENT = change, or on a mask with no transparency WHITE = change, black = keep; the reply says which it read. The mask guides rather than stencils, so the change can blend a little past its edge. Runs on gpt-image-2.5 (`refs.mask` in hermoso_capabilities): leave `model` empty or name it; another named model is refused, free. Needs refImages; keeps the source image's frame, so aspectRatio is not applied.

Static-ad edits + variants

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.

ParameterTypeDescription
imagerequiredstringthe image to edit: URL, Library item URL, upload_file URL or local path
instructionrequiredstringthe change to make, in plain words (pass the user’s own words for a removal or plain photo edit)
removalbooleantrue when the edit REMOVES text, branding, a logo, a watermark, a person or an object, so the brand name and logo are not re-added
logoboolean
maskstringoptional mask image (URL or local path) marking the region to change: transparent = change, or white = change on an opaque mask
dryRunbooleantrue = return what this edit usually costs in credits and render nothing
fixLabelbooleanfalse = leave the product label exactly as rendered: no product lookup, no label check, no re-print (default on when the ad shows the brand’s saved product)

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.

ParameterTypeDescription
imagerequiredstringthe finished static ad: URL, Library item URL, upload_file URL or local path
headlinesstring[]your own headlines to test (up to 10); omit to have them written
countintegerhow many headlines to write when `headlines` is omitted (default 5)
briefstringwhat to test, e.g. "price-led vs outcome-led" or "speak to busy parents"
fixLabelbooleanfalse = leave the product label exactly as rendered: no product lookup, no label check, no re-print (default on when the ad shows the brand’s saved product)

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.

ParameterTypeDescription
imagerequiredstringthe finished static ad: URL, Library item URL, upload_file URL or local path
aspectRatios"1:1" | "4:5" | "9:16" | "16:9" | "3:4" | "4:3"[]target canvases (default 1:1, 4:5, 9:16)
fixLabelbooleanfalse = leave the product label exactly as rendered: no product lookup, no label check, no re-print (default on when the ad shows the brand’s saved product)

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.

ParameterTypeDescription
imagerequiredstringthe finished static ad: URL, Library item URL, upload_file URL or local path
languagesrequiredstring[]target languages, by name
fixLabelbooleanfalse = leave the product label exactly as rendered: no product lookup, no label check, no re-print (default on when the ad shows the brand’s saved product)

YouTube / social thumbnails + video covers

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 `bakedUiText` stay verbatim in the user's language.

ParameterTypeDescription
frameworkstringconcept framework id (default 'posed_portrait') — before_after · social_ui · three_step · screenshot · posed_portrait · posed_action · specific_day · graphical · landscape · map_aerial · product · adding_text · repetition · size_difference · news_clip · amplified_reality — or your own concept in words
frameworkRequestedbooleantrue ONLY when the USER named this framework — it is what authorizes a text-carrying framework (social_ui / news_clip / specific_day / map_aerial) to bake its short UI label
sceneBriefstringwhat the thumbnail depicts — the concept in one dense sentence, rendered exactly
topicstringthe video's topic — used to pick the hero object when you don't name keyElements
headlinestring2–4 word headline. Typeset OVER the finished render by default (free, always legible); newlines split it into stacked lines
headlineLinesstring[]explicit headline lines (up to 3) — overrides splitting `headline` on newlines
bakeTextbooleandefault false. true paints the headline INTO the generation — only on an explicit user ask; it leaks garbled text elsewhere in the frame
bakedUiTextstringshort label for a text-carrying framework (a chat bubble, a DAY N badge, a news lower-third, a map callout) — needs frameworkRequested:true
overlayStylestringheadline style: beast (default), fire, neon-lime, clean-glass, marker, or your own CSS declarations
fontstringheadline font: Anton (default) or any Google Fonts family
headlinePlacestringbottom (default), top, center, or a 0-1 fraction from the top; never over the face
faceImagesstring[]up to 3 face photos (URLs or local paths) — each becomes a locked CHARACTER identity, in order
peopleobject[]people described in prose instead of by photo (each still gets the chosen expression)
castGenericPersonbooleanpass true only after the user has explicitly chosen a generated stranger over their own face
emotionstringthe expression on the face (default 'shock') — shock · hype · fear · confusion · determination · smug · charisma · disgust · awe · rage · laugh, or your own phrase
emotionsstring[]render one variant per emotion (variants = emotions × takes, max 16)
takesnumbercamera takes per emotion, 1–4: designed framing / low-angle hero / extreme close-up / wide dutch tilt
variantsnumberhow many thumbnails to render (default 1, max 16). Each is its own billed render — offer a set of 4 rather than assuming
aspectRatiostring'16:9' (YouTube, default) / '9:16' (Shorts) / '4:5' (Instagram) / '4:3' / '1:1'
keyElementsstringsignature props / effects that make it pop — oversized, flying toward camera
locationstringplace, time of day, weather, atmosphere
compositionstringoverride the default large-foreground-subject composition
backgroundstringoverride the default bold saturated colour-field background
rimColorstringcolored back+hair light — ONLY when the user names one: 'ice-blue' / 'neon-magenta' / 'toxic-lime' / 'amber-gold' / 'pure-white'
restrainedGradebooleantrue for a calm / premium / muted look instead of the default punchy poster grade
logostringa brand logo URL or path to place into the composition
logo3dbooleanfirst turn the flat logo into a volumetric 3D render (one extra billed image), then composite that
logoPlacement"auto" | "overlay" | "in_scene"overlay (exact, flat) | in_scene (the real file printed onto a blank panel in the shot; with logo3d, a 3D render of it, checked); auto: in_scene with logo3d
splitobjectsplit/panel LAYOUT — only when the user asks for one ("split", "before/after", "versus screen"). "X vs Y" as a SCENE stays one unified frame
referenceobjectfields YOU extracted by eye from a reference thumbnail. Extract ALL of: brief (one dense sentence on the concept), subject (pose/action generically, NEVER a specific identity), elements, location, composition, background, split (boolean), split_count, person_count (0-3), emotion (one of the 11 presets or 'other'), emotion_detail (one vivid sentence covering eyes, brows, mouth, head angle). emotion + emotion_detail carry the reference's actual facial performance, which is the single biggest CTR lever on a face; split/split_count reproduce its panel structure. The reference image itself is never sent to the model
tweakobjectsurgical pixel-faithful edit of a FINISHED thumbnail (needs sourceImage): kind emotion / background / background_color / rim_light, or any other kind with the edit in words as value
sourceImagestringthe finished thumbnail URL a `tweak` edits; tweaks chain, so feed each accepted output into the next
forceGeneratebooleanrender the 'screenshot' framework anyway (it is normally a real video frame, not a generation)

raw playground: voice

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

ParameterTypeDescription
textstringONE VOICE: the script to speak (≤900 characters)
enginestringvoice-engine id: one voice defaults to 'seed-audio'; a dialogue defaults to the dialogue model ('eleven-dialogue'); or 'eleven-v4', 'minimax-speech', 'kokoro'… (hermoso_capabilities)
voicestringONE VOICE: a preset from the chosen engine (e.g. 'Aria'/'George' on eleven-v4, 'stokie_en' on seed-audio); omit for the engine default
linesobject[]DIALOGUE: the turns in speaking order
dryRunbooleantrue = the exact credits only, nothing made or charged

generate_sfx#

Generate a sound effect

MAKE a sound effect or ambience that does not exist yet, from a description, as an MP3: a whoosh, a door slam, a cash-register ding, rain on a window, a busy cafe. (find_sound FINDS an existing sound, a meme or trending TikTok sound; this MAKES one.) seconds = length, 0.5-30 (default 5); loop:true for a loopable bed. engine: omit for Seed Audio 1.0 (length and loop are asked for in words, so the length is approximate), or 'eleven-sfx' (ElevenLabs Sound Effects v3: exact length, a real loop flag, description ≤450 characters). Lay it under a video with post_edit (music.track takes its link). dryRun:true returns the exact credits and makes nothing; the price quoted is the charge. Paid, priced by length (hermoso_capabilities -> sound effects).

ParameterTypeDescription
describerequiredstringthe sound in words: what makes it, where, how it moves, e.g. 'a fast whoosh past the ear' or 'a busy cafe, cups and low murmur'
secondsnumberlength in seconds, 0.5-30 (default 5)
loopbooleantrue for a loopable bed
enginestringomit for 'seed-audio' (Seed Audio 1.0), or 'eleven-sfx' (ElevenLabs Sound Effects v3)
dryRunbooleantrue = the exact credits only, nothing made or charged

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.

ParameterTypeDescription
promptrequiredstringthe music in words, e.g. 'lo-fi jazz, brushed drums, 80 bpm'
secondsnumbertrack length, up to 300
modelstringminimax-music-3 or lyria-3.5

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

ParameterTypeDescription
promptrequiredstringthe writing task / question
rawbooleanRAW MODEL ACCESS: send the prompt with NO Hermoso system prompt — the model answers as itself rather than as an ad copywriter. Use it whenever the ask is not marketing copy (analysis, code, extraction, a plain question). Default false: the copywriter framing is applied.
modelstringa writing-model id from hermoso_capabilities (a Claude / Gemini / GPT / Llama / DeepSeek id) — omit for the default

video / avatar / stitch

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

ParameterTypeDescription
creativerequiredobjectthe FULL structured output of plan_ad (must contain video_storyboard)
creatorstringCAST A SAVED CREATOR in this ad — their id from list_creators, or the name you know them by (“Sarah”), or a PRESET AI creator from list_creators presets by exact name or id (free, no generation). Their saved portrait becomes the on-camera identity for the whole spot, so the same face carries across every act and across every ad you render for this brand (casting skips the portrait we would otherwise generate). Omit to let the ad cast a fresh person — EXCEPT for a CREATOR account (onboarded from their own @handle): their own saved likeness is cast by default when the plan has a person on camera and the account is on a paid Hermoso plan (a free account gets a fresh AI person), and the read-back says `default:true`; pass "none" to render without them. Refused for free, with nothing rendered, if the name matches nobody or more than one creator, if an explicitly named creator is cast on a plan with nobody on camera, or if a REAL person is cast on a free plan. 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.
faceRoute"face_lane"'face_lane' records the user's statement "I own the rights to this face" for the real person in their reference (paid plans; the likeness terms apply). Set it only when the user picked that option after a render said it could not use that face, and send the same request again; it then renders through Hermoso's face library at the normal price. Never set it on your own. The other options are a different `model` or one of the user's saved `creator`s.
modelstringvideo model id from hermoso_capabilities (default: the engine the router picks for the plan: seedance-2.5 for every ad, a person talking to camera included). Naming one is a DELIBERATE pick — the server asks before ever swapping it (no silent fallback)
durationSecondsnumbertotal ad length in seconds, 4–180 (clamped). Omit to keep the plan’s own length, almost always right: this only RE-TIMES the authored board; to change the ad’s length, re-run plan_ad with durationSeconds. A length that fits ONE clip renders in one pass; longer is stitched from acts of the model’s clip maximum (15s on most, 30s on the longest-clip one), remainder last. dryRun:true shows the split free.
aspectRatiostringoutput aspect ratio, e.g. 9:16 (default) / 1:1 / 16:9
resolution"480p" | "720p" | "1080p" | "4k"Omit = the default tier, hermoso_capabilities defaultResolution (720p), the tier its `credits` prices are for. 1080p is ~2.25x, 4k more, 480p less: quote creditsByRes and pass the tier you quoted. A tier a model's `resolutions` lacks renders at its best one.
captionsbooleanburn the plan's per-scene on-screen words as caption pills. DEFAULT FALSE on every recipe — set true ONLY when the user asks for on-screen text or captions; no recipe turns them on by itself
endCardbooleanappend the branded end card. DEFAULT FALSE on every recipe — set true ONLY when the user asks for an end card (a clone of a video that had none should not grow one)
musicboolean | stringmusic bed: OFF unless asked. true = the plan's own music line; or the bed in words ('lo-fi jazz, brushed drums'); false = none
musicModelstringcompose the bed on minimax-music-3 or lyria-3.5, skipping the free library
boardbooleanpaint the storyboard board first (default: on for UGC and cinematic spots, off for a product-only commercial); false = render from the text shot list
foundations"choose"product commercial / cinematic: 'choose' paints ONLY the foundations (identity sheet + four-up moodboard, or hero + location) and returns them, no video — then call again with moodboardPick + foundationImages
moodboardPickintegerwhich moodboard panel (1-4, left to right, top to bottom) the storyboard follows; default 1
foundationImagesobjectimages a foundations:'choose' call returned, reused as-is (not repainted)
lockupbooleanbrand wordmark + tagline composited over the closing seconds. DEFAULT FALSE — set true ONLY when the user asks for branding on the close
textStylestring | objectTHE LOOK of captions and the end card, only with captions or endCard and only when the user described one: the look in WORDS ("chunky yellow comic letters, purple outline"), a preset (editorial: big serif title + small italic line; bold: condensed caps, outline; minimal; handwritten; boxed; pill, the default), or fields. "TITLE · small line" puts the part after the dot on a second line.
ttsVoicestringvoiceover voice name (e.g. Rachel / George) when the plan voices over
dryRunbooleanreturn the routing decision (single pass vs stitched acts, resolved model + act lengths) and the estimated credits the real render reserves, WITHOUT submitting a render — free, nothing charged
allowGenericProductbooleanproceed even though this brand has NO product photo on file and the ad features a product — the packaging will be INVENTED. Only pass true after telling the user that and hearing they are fine with a generic stand-in
screensstring[]real screenshots of the app or site for the shots that film it on a screen (URLs, upload_file URLs or Library ids), laid over them as cards
screenChoice"brand" | "site" | "without"the user's answer to needsAppScreens: brand = their saved app screens, site = found on their website (saved to the brand), without = none, device kept turned away
insertsobject[]the user's own screens laid over the finished video at these seconds (each item's look says how), sound running underneath (about 2 credits). The film always opens and ends on moving footage. Omit to use plan_ad's creative.inserts; [] renders without them. plan_ad with inserts writes the board around them.

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.

ParameterTypeDescription
configrequiredobjectMUST include config.template: 'custom' or a preset id, plus its fields; language ('Portuguese') for its own words. PRESETS: 'slideshow' (IMAGES, TikTok photo mode / Reels 1080x1920, or size:'4:5' feed carousels; no branding): { slides:[{text, sub?, image?, blur?, background?, position?}] (2-35; words never rewritten), style? ('tiktok-classic'|'clean-minimal'|'note-style' or a look in words), textStyle?, video?:true (+ an MP4, which takes config.music like any video) }; 2 credits, +1 per slide past 5, +2 for the MP4. 'imessage-chat' (VIDEO ~15s): { thread:{contactName, messages:[{from:'them'|'me', text?, product?:{image,title,domain}}]}, theme?, endCard }. 'chatgpt-chat' (VIDEO): { question, answer (may **bold** the brand), productImage?, endCard }. 'apple-notes' (VIDEO): { title, lines[], theme?, endCard }. 'value-prop' (VIDEO ~17s): { hook ≤40ch, claims[3-5 ≤34ch], productImages[2-3], palette[], endCard }. 'static-mockup' (IMAGE): { style:'imessage'|'notes'|'card', size?:{w,h}, ...fields }. 'airdrop-carousel' (VIDEO): { brandName, products:[{image, title?}] (3-16), endCard }. 'app-ui-tour' (VIDEO): { hook?, appName, iconImage?, beats:[{screenImage, caption}] (2-6), endCard }. 'imessage-cascade' (VIDEO): { notifications:[{sender, text}] (4-8), backgroundImage?, endCard }. 'photo-grid' (VIDEO): { title?, photos:[{image, label?}] (4-9), endCard }. 'vignette' (VIDEO): { hook, lines[2-4 ≤40ch], heroImage, endCard }. 'kinetic-type' (VIDEO, own SFX): { phrases[3-6 ≤34ch], productImages?[≤4], endCard }. 'myth-vs-fact' (VIDEO with a real VOICEOVER, small extra charge): { pairs:[{myth ≤50ch, fact ≤60ch}] (2-4; [brackets] accent), endCard }, real truths only. 'carousel' (IMAGES, 5-10 branded 1080x1080): { cover:{hook?, title}, slides:[{headline, support?, stat?:{value, label}}] (3-8), cta:{headline, cta?, domain?}, productImage?, logo? }. endCard = { headline, cta, domain?, logo?, color? } is OPTIONAL on every video preset: include it ONLY when the user asked for an end card (a closing card with their logo); without it the video ends on its own last frame. palette and fontStack optional.

MOTION GRAPHICS IN ONE CALL

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 for a few credits. 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.

ParameterTypeDescription
recipe"type" | "logo" | "launch" | "product" | "social"type, logo, launch, product or social; help:true lists the recipes
hookstringopening line; '*word*' = italic accent
linesstring[]more short lines, in order
taglinestringline shown under the headline or logo
namestringdefault: the saved brand's
logostringlogo image: https, upload_file URL or Library id; default the brand's
imagesstring[]pictures, hero first (https, upload_file URL or Library id)
size"16:9" | "9:16" | "1:1" | "4:5"frame shape: 16:9, 9:16, 1:1 or 4:5
secondsnumberlogo 4-10, others 6-60
mood"elegant" | "epic" | "upbeat" | "hype" | "calm" | "tense" | "playful"music and pace
musicstring'off' = silent
theme"light" | "dark"logo: dark for a light logo
energynumberhow much the film keeps moving, 0 to 1; 0 films exactly what is written
movingbooleantrue: one thing physically happens in each picture, filmed by a video model. false: stills moved by a virtual camera, far cheaper. Default true for launch, product, social
movesobject[]per scene, what happens in its picture; omit and the planner writes them
briefstringwhat the film is about, for the planner
modelstringimage-to-video model id from hermoso_capabilities; default is the best value for short shots
resolutionstringthe moving shots: 720p or 1080p (default) where the model has them
specobjectthe whole film as JSON, from a quote or a saved film; help:true shows the spec language
projectUrlstringa past film's url; returns its saved spec to edit and render again
helpbooleantrue returns the spec language and recipes; renders nothing
previewbooleantrue renders a 1-credit contact sheet to approve before the full render
confirmbooleantrue renders the film for real credits; without it you get a free quote

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.

ParameterTypeDescription
videoUrlrequiredstringthe served URL of the video to finish (from a previous render/job)
headerstringheader pill copy, ≤40 chars (required when pills is on)
substringaccent sub-pill copy, ≤34 chars (usually the product/brand)
pointsstring[]3-4 proof points, ≤44 chars each
accentstringbrand accent hex for the sub-pill
pillsbooleandefault true — set false for a grain-only pass
grainbooleandefault false — anti-AI film-grain finish

post_edit#

Post-production edit

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

ParameterTypeDescription
videoUrlrequiredstringthe video to edit: a render / Library URL, a direct file, or a public post link
opsrequiredobject[]the ordered edit plan (max 6 ops)
brandNamestringoverride the workspace brand name
domainstringoverride the brand website
accentstringoverride the brand accent hex
dryRunbooleantrue = return the estimated credits this edit reserves and run nothing

edit_timeline#

Compose an edit (timeline)

Compose ANY edit or transition yourself; there is no preset list. Segments on an output timeline (later ones drawn on top; overlapping ones ARE the transition), each animated by keyframes, plus your own HTML overlays and masks, compiled server-side into one render. Whip pan, zoom through, push, spin, speed ramp, slow motion, freeze frame, reverse, flash, J-cut / L-cut, circle or shape wipe, split screen, picture in picture: all composed from the fields below. Local render, the flat post_edit price (~2 credits, overlays included); a `generate` segment (a paid generated transition shot) only when the user explicitly asks for one. LOOK FIRST AND AFTER: video_frames(url, start, end, fps) shows exact frames free, so find the moment (the second the egg tips) before you cut, and check the seam of what you rendered before you call it done. SOURCES: src is 'this' (videoUrl) or any Library / render URL, direct file or PUBLIC TikTok / Reel / Facebook / X / YouTube post link (a viral hook's link works as is). in/out are SOURCE seconds; out:'payoff' cuts just before the hook's payoff, found from its motion. AUDIO: audio.tail:'payoff' carries the hook's OWN payoff sound across the cut (the egg's real splat over our clip); tail seconds = an L-cut; lead = a J-cut; gain dB or keys; mute. A VIRAL HOOK + THEIR PRODUCT: start from the hook. A clip matched to an unrelated hook never reads as one video, so write the clip AFTER the hook for it: a linking script + shot brief (the first line answers the hook, e.g. 'still waiting for the egg to land... anyway, come check out our restaurant'; what to film so it follows on; 5-15 s; then the pitch). The user records it, or you generate it (render_ad / generate_video, cost quoted first, on their OK); then join here: the hook with out:'payoff' and audio.tail:'payoff', then their clip. Match an existing unrelated clip only if they insist. A {generate:{prompt, seconds 3-8}} segment (a generated transition-only shot, paid, postEditTimeline) is never suggested; build it only when they explicitly ask for one. LINK FIRST: an effect alone never connects two unrelated clips; the link comes from what is in the frames. (1) Match cut, the default: video_frames (with its MOTION readout) on the hook's last second and across the other clip; pick the out-point AND the in-point (in: seconds, not always 0) where a motion direction, a screen position or size, a shape, a surface, a gesture or a gaze carries across, then ride the effect on that shared motion. (2) Its host names the hook in the first line. If the two share nothing, say so and offer the follow clip made for the hook. PRO, NOT IMOVIE: ease every curve (never linear on a move); keep the picture filling the frame through a move (scale up while it moves: two frames sliding side by side with a seam is the amateur tell); hide the handoff under the fastest, blurriest frames; carry direction into the next shot; cut on motion; end every effect cleanly; 0.2-0.6 s in total; a sound whose peak lands on the handoff (sfx whoosh at handoff minus 0.45 s, or the hook's own payoff sound). Moving segments get a real shutter blur automatically (motionBlur). EVERY SEAM IS MATCHED AUTOMATICALLY, hard cuts too: each cut is measured and the incoming clip graded (exposure, white balance, black level), grained UP (never smoothed) and softened while it moves toward the outgoing one; the reply gives before/after deltas per seam. match (timeline: every cut; segment: the cut into it): 'auto' default, 'off' for a deliberate contrast, or {grade, level, grain, blur: false to skip one, strength 0-1}; a segment's own constant exposure / contrast / saturation replaces the automatic grade. EVERY STITCH CHANGES THE SHOT: where two different clips (or two non-contiguous moments of one) meet at the same framing, the incoming one gets a simple crop of the same footage, varied: zoomed in on the subject, back out to the wide, or reframed off-centre (reframe: 'off' or the step 1.1-1.5; a segment with its own framing, a card or a transition is left alone). Still yours: subject size and headroom (scale it, never a jump from a third of the frame to two thirds) and sound (a 0.25-0.5 s J/L-cut, never a sonic wall). A clip placed after a hook gets a BUDGET (total - intro = its surviving window, and what was dropped). The plainest thing that links wins: a straight cut on action beats a decorative effect; over 0.5 s is too long in anything under 20 s; never flash more than 3 times a second. RECIPES (c = the cut second, adapt freely): whip pan: A over its last 0.22 s x 0 to -0.22, scale 1 to 1.35, mblur 0 to 220, all ease in; B overlap 0.08, opacity 0 to 1 over 0.08, x 0.22 to 0, scale 1.35 to 1, mblur 220 to 0, all ease out over 0.3 s; whoosh at c-0.45. Zoom through: A over its last 0.35 s scale 1 to 3 ease in anchored on the object, blur 0 to 10; B overlap 0.12, opacity 0 to 1, scale 1.5 to 1 and blur 10 to 0 ease out over 0.4 s. Cut on action: A out ON the motion, B scale 1.08 to 1 ease out over 0.25 s, audio.lead 0.2. Speed ramp: speed [{src:t0,v:1},{src:t0+0.25,v:0.3}] then [{src:t1,v:0.3},{src:t1+0.1,v:2}] into the cut. Circle wipe: B overlap 0.5 + overlays [{mode:'mask', segment:1, start, end, html: a white div whose clip-path circle grows via @keyframes}]. Card (picture in picture: a proven ad playing in a rounded card over the host watching it, any length): the host segment full frame, then the clip ON TOP with at:0, fit:'contain' (crop to reframe it), scale ~0.6-0.7, y ~0.12, radius ~0.04-0.06; a card is not a cut, so it is never graded toward the host; duck it under the host's first line with audio.gain keys (the host's voice leads the switch) and end it on a hard cut at a sentence break. SELF-CRITIQUE: the reply carries a vision REVIEW of each seam (pro / ok / amateur, linked or not, with fixes; about 2 credits, review:false skips it) and the seam frames. When it says ok or amateur, fix what it names and re-run the same sources (twice at most) before presenting; on a re-run give a generated segment {src: its URL, between: true} so it is not paid for twice. Then look at the WHOLE result once with video_frames, not only the seams: the first frame is not black or frozen, no dead air over ~0.3 s at the head, no lone black, flash or repeated frame at a cut, and nothing static for more than ~4-5 s (recut it or add a re-hook). FOLLOW-UPS ('cut earlier', 'no splat', 'whip pan instead', 'use the second hook') re-run this with the SAME sources and the one change; the result echoes the resolved timeline (e.g. the found payoff cut) to edit from. Refusals are free and name the field.

ParameterTypeDescription
videoUrlstringthe video 'this' refers to (optional when every segment names its own src)
segmentsrequiredobject[]the clips on the output, max 12, later ones on top. Every animated field (scale x y rotate opacity exposure blur mblur brightness contrast saturation, audio.gain) is a constant or keyframes [{t: seconds into this segment | src: a second of the source, v, ease: linear|in|out|inout|hold}]; x and y are in canvas widths / heights, rotate in degrees, mblur in px along mblurAngle
overlaysobject[]HTML/CSS overlays animated with @keyframes, each shown between its start and end
sfxobject[]sound effects placed on the output timeline
sizestringcanvas: '9:16' default, '1:1', '4:5', '16:9', any 'W:H' (1:3 to 3:1, on a 1080 short side) or exact 'WxH' pixels
fpsnumberthe frame rate, a whole number from 24 to 60 (24, 25, 30, 60 are the usual ones). Leave it out: the canvas takes the rate of its own footage (the rate of most of it on screen), so no frames are invented; a faster clip is thinned, a slower one that does not divide it is motion-interpolated
motionBlurbooleandefault true: anything that moves gets a real shutter blur along its path
reviewbooleandefault true: a vision read of each seam (about 2 credits) comes back with the render, with the seam frames
match"auto" | "off" | objectevery cut: 'auto' default
reframe"auto" | "off" | numberevery cut: 'auto' default

video_frames#

Look at video frames

LOOK at a video's exact frames, free: one contact-sheet image of the frames from start to end at fps (e.g. a seam, 3.0-3.8 s at 10 fps) or at the listed times, tiles in reading order with each tile's second listed. Use it to find a moment before an edit and to check a render's seam after it. Takes a Library / render URL, a direct file or a public post link (a TikTok / Reel / Facebook / X post link is opened through one paid lookup, 1 credit; a file is free). SAVE STILLS: save:true returns full-size still images (frame grabs, up to 8) as URLs instead of a contact sheet, picked by times or by clips (numbered at the video's hard cuts, -1 = the last): a before / after from someone's own footage, a cover, a product shot. Compose with those URLs (make_template_ad template 'custom', a post) with no AI.

ParameterTypeDescription
urlrequiredstringthe video
startnumberwindow start in seconds
endnumberwindow end in seconds
fpsnumberframes per second in the window (default ~16 frames across it)
timesnumber[]exact seconds instead of a window (max 48; max 8 with save)
savebooleantrue = save the frames at times / clips as full-size stills and return their URLs
clipsnumber[]save: one still from the middle of each numbered clip (split at hard cuts; negative counts from the end)

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.

ParameterTypeDescription
videoUrlstringthe served URL of the master video to fix (not needed with `job`)
startSecondsnumberwindow start in seconds (not needed with `job`)
endSecondsnumberwindow end in seconds (window 1.5-8s; not needed with `job`)
promptstringwhat the replacement footage should show — describe the shot, matching the master's style. With `job`: the change to that block's picture, in words
jobstringa finished video-blocks film (explainer / faceless channel): its job id. Edits one block of it instead of splicing a window
blocknumberwith `job`: which block, 1-based (block 3 = 20-30s of a film of 10s blocks)
atSecondsnumberwith `job`: the moment in the film instead of `block` ("the line at 0:12" = 12)
linestringwith `job`: the new narration for that block, the exact words to say (said as written, never rewritten; refused for free when it cannot fit the block)
refImagestringoptional product/style anchor image URL
speechWindowsnumber[][][[start,end],...] windows with spoken lines — the fix window must not overlap these
dryRunbooleantrue = return the estimated credits this fix reserves and render nothing

THREE BUILT LANES

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 YouTube link (or Vimeo / Loom / Dailymotion / Streamable / Rumble / Wistia / Twitch / TED), a direct https .mp4/.mov/.webm, or a Hermoso /generated/ URL (upload_file turns a local file into one). NOT supported: TikTok / Instagram / Facebook links, and anything age-restricted, private, members-only, geo-blocked or still LIVE — those fail fast with the real reason and are fully refunded, so ask for a direct file or an upload rather than retrying. Source ~15s to ~600MB; only the first ~40 minutes is analysed (truncated:true says so). Cost: a ~7-credit hold settled to the exact transcription + encode cost, plus the clip-selection model's tokens as their own small event. RETURNS clips[] — each its OWN served mp4 URL, title, hook, ready-to-post caption, 0-100 score and source timecode. SUBTITLES ARE BURNED IN BY DEFAULT (slim white CAPS, thin black outline, bottom safe band, no box) because short-form is watched on mute; captions:false for clean footage. TIMING IS APPROXIMATE, NOT WORD-LEVEL — cues follow the transcript's per-sentence timestamps, split by character count; never promise frame-accurate sync. captionsBurned counts the clips that really carry a burned track and captionNote says why any are bare.

ParameterTypeDescription
videorequiredstringthe long video to clip — a YouTube/Vimeo/Loom/Dailymotion/Streamable/Rumble/Wistia/Twitch/TED watch URL, a direct https .mp4/.mov/.webm, or a Hermoso /generated/ URL
countnumberhow many clips to cut, 1-8 (default 4)
aspectRatiostringclip shape: '9:16' (default), '1:1', '16:9', any 'W:H', or 'keep' for the source framing
captionsbooleanburn subtitles into every clip. DEFAULT TRUE — a clip cut from a podcast or a talk is watched on mute, and the words are the product. Set false for clean footage. A clip whose window carries no readable speech is delivered bare rather than captioned with a guess, and the result says which.

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. Or pass cues to burn your own lines exactly, untranscribed (reel-style phrases, *stage directions*). burn:false returns only the .srt. Takes a video file or URL, a YouTube/Vimeo-style page, or a TikTok / Reel / X post link. No speech is refused and refunded. Runs in the background and lands in the Library.

ParameterTypeDescription
videorequiredstringthe video to subtitle
textStylestring | objectthe look: words, a preset or fields; omit for the default
burnbooleanfalse = only the .srt
auto"sentences" | "words"sentences (default) or words: timing unit for transcribed subtitles
wordsPerCuenumberwords per subtitle line when auto is words
cuesobject[]your own lines in seconds, no overlap, max 90 chars, no emoji

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 — most of them one-detail edits of the frame before, so it reads as movement rather than a slideshow), narrates each section with TTS, holds each picture PERFECTLY STILL for its own slice of the narration (the motion is the CUT RATE — a slow move on a still shimmers), then composites the end card (and any on-screen text you asked for) with the Chrome+ffmpeg engine the ads use (text is never model-painted, so it never garbles). BURNED ON-SCREEN TEXT IS OFF BY DEFAULT — the narration carries the point and the pictures carry the story, so the film ships clean unless the user asks otherwise; `captions:true` adds held key points and `subtitles:true` adds subtitles timed to the narration (see both). The stills lane is an image film WITH motion, not N video-model renders — that's what keeps it affordable. `style` picks the visual family: the default 'cinematic' is photoreal editorial; every other id is a STYLED, strictly non-photoreal look (illustrated / collage / clay / pixel …) that first renders ONE style-key image and then locks every scene to it, so the whole film holds one look. Cost at the default frame density: a ~130-credit hold for a 60s explainer on the default style, ~100 styled; `frameDensity:'lean'` roughly halves it and `'minimal'` (one picture per section) is ~30. All settle to the exact per-frame image + narration spend (a longer target = more sections = more). Takes SEVERAL minutes — one image render per frame; independent frames are painted concurrently, so it is far faster than the frame count suggests. Needs the writing model and a narration voice engine connected. NOT the tool for a short product ad — use render_ad or generate_video for those, and make_template_ad for the deterministic native formats.

ParameterTypeDescription
topicstringwhat the explainer should teach or explain — a topic or a short brief (host_episode: or pass `script`)
lane"blocks" | "stills"'blocks' (default) = 10-second video blocks, real motion, one narrated line per block; 'stills' = the picture film (a still about every 1.5s, narrated, no video model; cheaper). Quote either with dryRun.
format"explainer" | "faceless_channel" | "host_episode"blocks lane: 'explainer' (default; Gemini Omni 720p, 16:9 or 9:16, runs exactly the length asked) or 'faceless_channel' (a YouTube/TikTok faceless channel video; MiniMax H3 at 2K, 16:9 by default, five hard cuts per 10s block, runs exactly the length asked: a last part-block is rendered whole and cut). Omit and a history / kids / fairytale channel is a faceless channel video. 'host_episode' = the AI host episode (see the top).
creatorstringhost_episode: the host, a saved creator or a preset by name or id (list_creators)
hostImagestringhost_episode: a photo URL of the host instead (upload_file for a local file); a real person's face needs a paid plan
scriptstringhost_episode: the exact words, said verbatim and split at natural breaks into 4-30s pieces
fromDraftstringhost_episode: a finished draft's job id, to render it in HD (720p) with the same script, cameras, set and host
settingstringhost_episode: the set in words (default: written to fit the topic)
camerasstring[]host_episode: the rotation, ids frontal / three_quarter / close or framings in words; one entry = one fixed camera
thumbnailbooleanhost_episode: one thumbnail of the host (default true)
durationSecondsnumbertarget length 20-120s (default 60); drives the section count — ~10s of narration each, 3-8 sections
frameDensity"standard" | "lean" | "minimal"how many pictures per second of narration, and therefore what it costs. 'standard' (default) is a frame about every 1.5s — the density a stills film needs to read as a film rather than a slideshow; 'lean' is one about every 2.5s (the longest hold that still reads as a film, ~40% of the frames and ~40% of the cost); 'minimal' is one picture about every 3.5s, the cheapest and the longest any still is ever held, and it reads close to a slideshow. Only drop below the default if the user asked for something cheaper.
aspectRatio"9:16" | "16:9" | "1:1" | "4:5" | "3:4"'9:16' default (a faceless channel video and a host episode default to 16:9)
stylestringvisual style: 'cinematic' (default, photoreal); styled shortcuts editorial_collage, flat_vector, stickman, whiteboard, ink_marker, silhouette, storybook, paper_diorama, isometric, claymation, pixel_art, watercolor, fluffy_toy, low_poly, stylized_3d, studio_3d (the Kids default), mannequin; or ANY look described in words ('80s anime cel animation'), locked across every frame. Ask rather than pick silently; a styled look costs more.
channel"explainer" | "history" | "kids" | "fairytale"the CHANNEL TYPE — it sets the pacing, the narration register and the default look, and is orthogonal to `style` (a named style always wins): explainer (casual second-person, fast cuts), history (witty chronological retelling / documentary), kids (fastest, question-first, warm teacher), fairytale (slow, atmospheric myth or folklore). Default 'explainer'.
voicestringnarration voice name — omit for the default warm read
captionsbooleanturn ON-SCREEN TEXT on. DEFAULT FALSE, and leave it false unless the user asks — the narration already says the point and the pictures carry it, so the clean film is the better default. `captions:true` on its own burns SUBTITLES (see below), because that is what a caption is for: showing what is being said when the phone is on mute. Slim white text (CAPS in the stills lane), thin black outline, bottom safe band, no plate, no box.
subtitlesbooleanwhich on-screen text, once `captions` is on. LEAVE IT UNSET (or true) for SUBTITLES — every spoken word, in order, timed to the narration; no extra render, and there is NO cue limit, so the whole film is subtitled however long it runs (at most 4 words and one line a cue; 5 words / 32 characters in stills). Set it FALSE only if the user explicitly wants section HEADINGS instead: one short summary label held over each ~7-15s section. That is NOT what is being said — it is a label about it — so it is the wrong answer to "add captions" and to anyone watching on mute. `subtitles:true` also implies `captions:true`. TIMING: in the default blocks lane each cue is timed to its own spoken words (one listen to the narration, a tiny charge inside the quote; per-block timing, said, if it fails). In the stills lane cues are spread over each section’s measured narration by character count, exact at section edges, a few tenths off within one: it is not a word-level speech clock, so never promise frame-accurate sync there.
musicstringmusic bed under the narration, measured to sit about 14 dB under the voice and sidechain-ducked beneath it. Omit and the KIDS and FAIRYTALE channels get their recommended bed COMPOSED for this film — those two are the only channels a bed is due on unasked, and it costs a small flat fee; every other channel ships dry. 'off' forces silence. 'library' takes a free curated track only, and ships dry when none is on file. NAME A MOOD (upbeat / calm / warm / epic / tense / playful / elegant / hype / chill / dramatic) or DESCRIBE it in words to compose one on ANY channel, at the same fee. hermoso_capabilities reports the exact figure as explainerMusicCredits; quote it before you turn a bed on or pick a mood.
upscalenumberoptional FINAL upscale — 2 doubles each side, 4 quadruples. Captions and the end card are burned BEFORE it so they upscale with the frame. It is priced BY LENGTH and it is the expensive part — several times the cost of rendering the film itself. hermoso_capabilities reports the exact figures per length as explainerUpscaleCredits. Never turn it on unasked: quote the number and let the user choose.
endCardbooleanappend the branded end card. DEFAULT FALSE — set true ONLY when the user asks for one
brandNamestringbrand name for the end card — omit to leave it unbranded
dryRunbooleantrue = return the estimated credits this explainer reserves (its own pricing, stopped at the hold) and render nothing. Quote it before running one; try frameDensity lean or minimal when the balance is short.
fromFilmstringA PRESENTER ON A FINISHED FILM (only when the user asks for one): the job id of a finished video-blocks explainer or faceless-channel film. With `presenter`, that film comes back with a presenter speaking each block's line on screen; every block keeps its own picture and the film without the presenter is kept (cleanVideo). Never add a presenter unasked.
presenterobjectwith fromFilm: who presents and how. A face the video model flags stops it, nothing charged, with the choices to offer.

CONTENT FACTORY

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.

ParameterTypeDescription
jobIdrequiredstringthe id of the film that stopped (job_...), as get_job or list_jobs shows it
dryRunbooleantrue = return the credits the resume reserves and render nothing

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 / YouTube Short / 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.

ParameterTypeDescription
topicstringwhat the series is about, in the user's words
episodesnumberhow many episodes, 1-10 (default 1)
episodeTitlesstring[]your own episode titles, in order (sets the episode count)
durationSecondsnumberlength of EACH episode, 20-120 seconds (default 30)
referencestringa video link whose style every episode copies
aspectRatio"9:16" | "16:9" | "1:1"default: the reference's shape, else 9:16
stylestringoverride the look in words (e.g. "flat 2D cartoon") instead of the reference's
captionsbooleanburn subtitles of every spoken word. Default: on only when the reference itself burns its spoken words as subtitles, otherwise off; nothing else is ever put on screen (no app screens, URLs, cards or stickers) unless the user asks
voicestringnarrator: 'female' or 'male' (default follows the reference), or any narration voice name make_explainer takes
format"explainer" | "faceless_channel"renderer lane: 'explainer' (default; exact length) or 'faceless_channel' (2K, whole 10s blocks)
planOnlybooleanwrite the episode plan and style card, render nothing (a small planning charge)
planobjectthe `plan` a planOnly call returned, to run it without planning again
moreOfobjectMORE EPISODES: the `plan` an earlier call returned. Keeps its style and length, writes new episodes only
confirmbooleanREQUIRED true to start rendering (real spend). Without it you get a free quote
jobIdsstring[]SCHEDULE MODE: the episode job ids to schedule (finished ones are booked)
scheduleobjectSCHEDULE MODE: where and when the first episode posts; the rest follow every `everyHours` (default 24)
postCaptionsstring[]SCHEDULE MODE: one post caption per episode, in order (default: the episode title)

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.

ParameterTypeDescription
effectstringeffect id from the list
imagesstring[]its pictures, in the order listed: URL, local path or Library id
optionsobjectits listed options, e.g. {material:"knit"}; "custom" means use `describe`
textstringa line printed exactly, on an effect that takes one
describestringyour own words: added to the look, or the custom choice
aspectRatiostringone of its listed shapes
stillstringCLIP STEP: the finished picture to animate
speechstringCLIP STEP: a line said in the clip, on an effect that speaks
confirmbooleantrue = run it (real spend); without it, a free quote

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.

ParameterTypeDescription
promptrequiredstringwhat the sizzle should show — the product, the setting, the look
secondsnumberfinished length, clamped to 18-30s (default 25). The PAID hero render is always 15s regardless — this only changes how the cuts and cards are packed
refImagestringproduct packshot URL that anchors the real label — strongly recommended
aspectRatiostring'9:16' default; anything the Seedance row that renders it does not list falls back to 9:16
resolution"480p" | "720p" | "1080p" | "4k"hero-clip resolution and therefore the whole cost — DEFAULT '1080p' (≈1,040 credits); '720p' ≈470, '480p' ≈220, '4k' ≈4,130
specsstring[]up to 4 spec lines for the typeset cards, ≤26 chars each
ctastringclosing CTA line, ≤30 chars
brandNamestringbrand name on the cards — defaults to the workspace brand
musicMoodstringmusic-bed mood, e.g. driving / cinematic / upbeat
style"hypermotion" | "product_2d" | "typography" | "dark_minimal" | "color_pop" | "mixed_media" | "saas" | "product_showcase"the LOOK of the sizzle (same price for every look): hypermotion (Fast cuts, crash zooms and speed ramps), product_2d (The real product with playful hand-drawn 2D animation around it), typography (Product framed by huge bold letterforms in the brand colours), dark_minimal (Moody, premium, a single hard light on black), color_pop (Bright saturated colour, glossy, joyful), mixed_media (Editorial collage: paper, cut-outs and texture around the product), saas (For apps and software: floating screens, light trails, particles), product_showcase (Hero commercial: ingredients, splashes and a clean pack shot). Default hypermotion.

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

ParameterTypeDescription
dryRunbooleantrue = render NOTHING and return the credits this exact clip would hold (an estimate; the final charge is its actual cost). Free. Call this to price a clip before running it: without it the call renders and bills.
promptstringthe video prompt / shot description (for a refVideo edit, this is the transformation instruction); optional with `shots`
rawbooleanSend the prompt to the model exactly as written, without the wording Hermoso usually adds (packaging and label guidance, reference lines, colour names for hex codes, quality hints). Use it when you want the model's own interpretation rather than Hermoso's render craft. Two vendor-required fixes still apply: extra @ImageN tokens are dropped and an over-long prompt is trimmed at a sentence (on Veo it is refused with the length and the cap instead). Billing, durable delivery and per-model validation are unchanged.
refImagestringlocal path or URL to anchor the first frame. Using a real person’s face, likeness or voice confirms you are them or have their consent, take full, unlimited responsibility for its use and will cover any claim against Hermoso (hermoso.ai/terms). Paid plan only; recorded.
speakstringA PERSON SAYING THESE EXACT WORDS TO CAMERA — the default for any "make my photo talk" / spokesperson / talk-to-camera ask. Pass with `creator` or a portrait as `refImage`: the video model films them saying it in their own voice, matched to who they are, and the length follows the words (leave durationSeconds out). `prompt` is then the staging (e.g. "natural", "walking in a park"). Real footage, not an animated photo; generate_avatar is the animated-photo look, only when asked for by name
creatorstringSTAR A SAVED CREATOR in this clip — their id from list_creators, or the name you know them by, or a PRESET AI creator from list_creators presets (exact name or id; free, no generation). Their saved portrait rides first among the references as the on-camera person, with their saved consent, exactly as render_ad casts them; a real person saved from a photo keeps their real face on camera. An unknown name is refused by name, nothing charged.
anglesbooleanOPTIONAL, OFF BY DEFAULT: with `creator`, also send the extra views that creator already has saved (their pose plates, up to 2) beside their portrait. A test showed no visible improvement over the portrait alone, and each extra view adds about 25 s before the render starts, so leave it off unless asked. Views are skipped when the face library is busy, and the render always goes ahead on the portrait.
refImagesstring[]SEVERAL reference images (paths or URLs: a person, products, a place) that must all appear. A model uses up to its `refs.max` in hermoso_capabilities (one-image models take the first); with `refs.promptAddressed`, name them Image 1, Image 2… in order. minimax-h3-max-ref takes 9 and keeps each as a reference, not a first frame.
refVideostringURL of an existing video to EDIT rather than generate from scratch — the clip is transformed per your prompt, keeping the SOURCE clip’s aspect ratio and, on models hermoso_capabilities marks `sourceLength`, its LENGTH (their `durations` are the per-second price ladder; a durationSeconds sent is reported as unused). Trim the source to change the length. Omit `model` for the default editor, or name a model whose videoEdit is true in hermoso_capabilities (a named model that cannot edit is refused, nothing charged); refImage rides along as the look of what the edit adds. With extend:true this is instead the clip to EXTEND. Omit to generate a fresh clip.
endImagestringlocal path or URL of the LAST frame: the clip travels from refImage (required with it) to this image. Only models with endFrame true in hermoso_capabilities take it; any other named model is refused by name, nothing charged.
loopbooleantrue = a seamless loop whose last frame flows back into its first. Only models with loop true in hermoso_capabilities; needs refImage and cannot be combined with endImage.
shotsobject[]MULTI-SHOT: one clip cut into these shots, in order; the seconds add up to a length the model renders. Replaces `prompt`. Only models with multiShot true in hermoso_capabilities; a shot prompt over that model’s shotPromptMaxChars (Kling 3.0: 512) is refused free, never cut.
extendbooleantrue = EXTEND refVideo by durationSeconds (default 5) with what your prompt says happens next, as ONE clip. By default it CUTS to a NEW SHOT: new angle, same scene, room sound carried, no music at the cut. A named model (any i2v) renders it.
extendMode"cut" | "continue"with extend: 'continue' = the SAME take past its last frame, only when asked seamless (models with extend in hermoso_capabilities).
interactionIdstringwith extend:true on an Omni model: the interactionId returned by an earlier render on that model — continues it from its own stored context instead of re-uploading refVideo.
durationSecondsnumberlength of THIS ONE clip in seconds: one of the CHOSEN model’s listed durations in hermoso_capabilities (they differ per model, 4–8s on the short ones up to 30s). One continuous generation, so a longer ask than the model’s longest clip is REFUSED, nothing charged (never quietly truncated). Past ~15s NAME a long-clip model in `model`. For a longer spot use plan_ad with durationSeconds then render_ad, which stitches acts (on a 15s model, 40s = 15+15+10).
aspectRatiostringdefault '9:16'
modelstringvideo model id from hermoso_capabilities; a named model is never swapped without asking. Omit to let the router pick; its default per job (jobEngines in hermoso_capabilities, from watched side-by-side tests) is seedance-2.5 for product, lifestyle, a person talking to camera and 30s single takes (cheaper engines broke the product there). A model the user picks is kept; never switch it
faceRoute"face_lane"'face_lane' records the user's statement "I own the rights to this face" for the real person in their reference (paid plans; the likeness terms apply). Set it only when the user picked that option after a render said it could not use that face, and send the same request again; it then renders through Hermoso's face library at the normal price. Never set it on your own. The other options are a different `model` or one of the user's saved `creator`s.
resolution"480p" | "720p" | "1080p" | "4k"Omit = the default tier, hermoso_capabilities defaultResolution (720p), the tier its `credits` prices are for. 1080p is ~2.25x, 4k more, 480p less: quote creditsByRes and pass the tier you quoted. A tier a model's `resolutions` lacks renders at its best one.
cameraMove"orbit" | "orbit_left" | "orbit_half" | "orbit_full" | "rise" | "crane_up" | "push_in" | "pull_back" | "reveal"A named camera move around the still in refImage (orbit = a quarter turn, the default; orbit_half / orbit_full = a half turn / a turntable). Only minimax-h3-max-camera renders one: with model omitted it is picked; any other named model is refused, nothing charged. Needs refImage.
cameraTrajectoryobject[]Your own ordered camera path, 2 to 12 keyframes, for the camera-controls model only (same rule as cameraMove; overrides it). The first pose is held until its time and the last pose is held to the end. A value outside these bounds is refused by name, nothing charged.
ttsScriptstringvoiceover script to speak
ttsVoicestringvoice name, e.g. Rachel / George
musicModelstringcompose the bed on minimax-music-3 or lyria-3.5, skipping the free library
musicMoodstringthe mood of the music bed (upbeat / calm / warm / epic / tense / playful / elegant / hype / chill / dramatic; default warm). It does not decide WHETHER there is one: a clip with no audio track (a "silent" model in hermoso_capabilities, or one that came back mute) gets a bed composed and CHARGED automatically at explainerMusicCredits. audio:false = silent, no bed, no charge.
audiobooleandefault true. false = render SILENT: no native model audio, no music bed, and no bed charge held or billed. This is the ONLY way to decline the automatic bed (see musicMood) — leave it alone for anything that should have sound, and do not combine it with ttsScript.

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.

ParameterTypeDescription
imagerequiredstringlocal path or URL of the presenter portrait. Using a real person’s face, likeness or voice confirms you are them or have their consent, take full, unlimited responsibility for its use and will cover any claim against Hermoso (hermoso.ai/terms). Paid plan only; recorded.
scriptrequiredstringthe words the avatar speaks
voicestringvoice name (Aria / Sarah / George / Adam). Leave it out and the voice matches the person in `image`; if nobody can be read, the call is refused free asking for one
enginestringleave out for the standard engine; only an engine listed in hermoso_capabilities avatarEngines is accepted
resolutionstring'720p' (default) or '480p'
promptstringmotion direction, only for an engine other than standard that hermoso_capabilities lists
seedintegerfixed seed, only for an engine other than standard
acceptQueuebooleanonly for an engine hermoso_capabilities marks oneAtATime: wait in line
dryRunbooleanreturn the credits this exact job would hold, without rendering

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.

ParameterTypeDescription
scenesrequiredobject[]array of scene objects (visual + optional voiceover/seconds)
aspectRatiostringoutput aspect ratio, e.g. 9:16 (default) / 1:1 / 16:9
voiceoverstringfull voiceover script spoken across the scenes
voicestringvoiceover voice name, e.g. Rachel / George
resolutionstring720p (default), 1080p for full detail, or 480p for a cheaper draft
modelstringvideo model id from hermoso_capabilities — omit to let the router pick
durationSecondsnumbertotal spot length in seconds (defaults to the sum of the scenes’ seconds)

get_job#

Get render job read-only

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

ParameterTypeDescription
idrequiredstringthe job id: starts with job_, from a render tool's jobId or from list_jobs (generate_voice and generate_sfx return their file directly and have no job)

assets

list_library#

List library read-only

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), kind, model, and age. Free, read-only.

ParameterTypeDescription
kind"image" | "video" | "document" | "all"filter by asset kind (default 'all')
uploadsboolean
limitnumbermax assets to return (default 20, max 60)

delete_library_item#

Delete from Library destructive

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.

ParameterTypeDescription
idsstring[]Library ids from list_library
urlsstring[]file links
confirmbooleantrue deletes

fetch_asset#

Fetch asset read-only

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

ParameterTypeDescription
urlrequiredstringthe asset url or /generated/ path
namestringoptional filename for the download

post-production & analysis

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.

ParameterTypeDescription
urlrequiredstringthe video URL (a served /generated/ path or a public http(s) video)
framesbooleanalso return the frames as images
wordsobjecttrue | 'only': each spoken word's start/end
anchorsobject[]{afterWord|beforeWord|atWord, occurrence?, offset?} -> s

repurpose_transcript#

Repurpose a video transcript

Turn a VIDEO into WRITTEN pieces: a summary, YouTube chapters (first at 0:00, at least 3, each 10s+), chapter summaries, an X thread (every post under 280 characters), a blog post, timestamped verbatim quotes, or any piece in your own words (pass the words as a format). The video: a YouTube link (read from its captions), a video file URL, a Library id, an upload_file URL or a social post link (read by watching it); or the text itself, when you already have it from analyze_video (which is the tool that just reads a video back). Several formats share ONE read. Priced by the video's length before anything is spent: without confirm it returns the exact credits and spends nothing; confirm:true writes and charges exactly that. Only the paid minutes are read. Every piece is written only from what the video says. Built-in skills youtube_script and retention_skeleton (get_skill) write long-form scripts.

ParameterTypeDescription
videostringa YouTube link, a video file URL, a Library id (L0…), an upload_file URL or a social post link
transcriptstringthe transcript text, when you already have it; then nothing is read
formatsstring[]summary, chapters, chapter_summaries, thread, blog, quotes, or any piece in your own words; default summary
instructionsstringyour direction for every piece: audience, tone, length, a call to action
languagestringthe language to write in, when not English
secondsnumberthe video length in seconds, when you know it: it prices exactly that, and anything past it is not read
read"auto" | "watch"'watch' reads a YouTube video by watching it rather than from its captions (one with no captions)
modelstringa Claude writing-model id from hermoso_capabilities; omit for the default
includeTranscriptbooleanalso return the transcript that was read
confirmbooleantrue only after the user agreed to the quoted price

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.

ParameterTypeDescription
querystringsound by name, moment or meaning, e.g. 'bad news reaction'
urlstringlink to a TikTok sound, a post, a meme-sound page or an audio file
picknumbercandidate number from the previous list to take instead of the first

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.

ParameterTypeDescription
imagestringstill picture URL; use this or video
videostringvideo URL to take the picture from, starting at videoStart
videoStartnumberseconds into the video where the insert starts
soundrequiredstringsound URL (find_sound returns one), or a video whose own audio is used
soundStartnumberseconds into the sound where the insert's sound starts
soundEndnumberseconds into the sound where it ends
secondsnumberfixed insert length, 0.2 to 6 seconds, instead of when the sound lands

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), strengths, and the single biggest fix. Use BEFORE spending on distribution, or to rank variants.

ParameterTypeDescription
urlrequiredstringthe ad asset URL (a /generated/ path or public URL)
kind"image" | "video"'image' (default) or 'video'
intentstringwhat the ad is trying to achieve, for goal-fit scoring

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.

ParameterTypeDescription
videorequiredstringthe source video URL
aspectRatiorequired"9:16" | "1:1" | "16:9" | "4:3" | "3:4" | "21:9" | "9:21"the target aspect ratio

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.

ParameterTypeDescription
videorequiredstringthe video: an https / upload_file URL, a Library id (L0…), or a local path on the CLI
placementsstring | object[]default ['everywhere']; a canvas, a WxH size, a platform word, or {placement, reframe}
reframe"pad" | "crop" | "ai"default 'pad' (free); 'ai' is paid and only when the user asks
padobjectthe pad's look: fill 'blur' (default) or a #rrggbb colour, blur 0-1, dim 0-0.6, position top/center/bottom or 0-1
captionsboolean | string | objecttrue = word-timed captions on every file (only when the user asks); a textStyle preset name or object sets the look
wordsPerCueintegercaptions: 1-8 words a cue (default 4)
languagesstring[]dub into these languages (paid), e.g. ['Spanish','de']; the original is kept too
namestringthe asset name the files are named after
dryRunbooleanprice only: nothing made, nothing charged

repurpose_image#

Repurpose a static ad into variants × formats

ONE finished static ad in, VARIANTS × FORMATS out, in one job, as a named file list (<name>_<variant>_<format>_<WxH>.png) with a manifest. The ad's text is read ONCE as the source of truth; each variant's change is made ONCE on the master; every format is derived from that master; every output is text-checked line by line and any line it lost is named (never re-rendered). Variants: {headline}, {language}, {swap:[{from,to}]} (exact words: a price, an offer, a CTA) or {edit} (any change in words), each with an optional {name}; the original is always included. Formats: default 1:1, 4:5, 9:16, 16:9, or 'everywhere', any canvas or a platform word (snapped to the nearest canvas an image model makes). At most 24 renders. dryRun:true = the exact price, which is what it charges. One resize alone is resize_ad.

ParameterTypeDescription
imagerequiredstringthe ad: an https / upload_file URL, a Library id (L0…), or a local path on the CLI
formatsstring[]default ['1:1','4:5','9:16','16:9']
variantsstring | object[]the versions besides the original; a bare string is an edit in words
namestringthe asset name the files are named after
fixLabelbooleanfalse = no product-label check (default on when the ad shows the brand's saved product)
dryRunbooleanprice only: nothing made, nothing charged

add_presenter#

Add a presenter to any video

Put a PRESENTER on ANY video, only when the user asks for one: their pick of person (`creator`: a saved or preset creator from list_creators; or `image`: a photo they gave; never invent one), filmed on a green screen saying the words, cut out and laid over the video in a bottom corner (default), a round badge, or split screen, lip-synced, block by block across the whole video (about 10 s and about 25 spoken words a block). The words are `script` (verbatim; refused free if it cannot fit) or, without one, written to fit from what the video says and shows (`brief` steers them). The video's own sound stays under the presenter unless baseAudio 'off'; the video without the presenter is kept. A face the video model refuses STOPS it with nothing charged: offer another creator, another photo, or the video as it is. dryRun:true = the exact price, which is what it charges. For a finished make_explainer film, make_explainer with fromFilm.

ParameterTypeDescription
videorequiredstringthe video: an https / upload_file URL, a Library id (L0…), or a local path on the CLI; up to 2 minutes
creatorstringthe presenter: a saved or preset creator, by name or id
imagestringor a photo of the presenter (a real person's face follows the real-face rules)
scriptstringthe exact words to say, used verbatim and split at sentence ends
briefstringwithout a script: what they should talk about
look"corner" | "badge" | "split"
positionstring'br' (default), 'bl', 'tr', 'tl'
scalenumber0.2-0.8 of the frame
voicestringa voice name, only when asked (default: their own voice)
baseAudio"under" | "off"the video's own sound: 'under' the presenter (default) or 'off'
captionsbooleansubtitles of what they say; only when asked
dryRunbooleanprice only: nothing made, nothing charged

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.

ParameterTypeDescription
videorequiredstringthe source video URL (a render, job result or list_library item)
startTimerequirednumberseconds into the clip where the new scene starts (1.625-60)
resumeTimerequirednumberseconds into the ORIGINAL clip where it picks up again after the new scene (2.625-60, at or after startTime)
promptstringwhat happens in the new scene: the action, the subject, the camera
secondsnumberlength of the new scene, 5-13 seconds (default 5)
refImagesstring[]up to 4 picture URLs of what should appear (the product, a person, a place)
refVideosstring[]up to 1 clip URL whose look or motion the scene follows
resolution"480p" | "720p"default 720p (rendered at 768p); '480p' is cheaper

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.

ParameterTypeDescription
videorequiredstringthe source video URL
engine"standard" | "flux" | "detail"default 'standard', the precision upscaler. 'flux' = the FLUX 3 video upscaler. 'detail' = rebuilds fine detail, costs the most
mode"precise" | "creative"FLUX only — 'creative' turns on its detail-enhancement pass; default precise

finish_draft#

Finish a draft in 1080p

FINISH a 480p Seedance 2.5 DRAFT in 1080p: the model regenerates THAT take (same scene, shots, motion, sound) at full resolution. Every 480p Seedance 2.5 render (generate_video / render_ad at resolution 480p) is a draft, finishable for 7 days; its result carries `draft` {id, expiresAt, finishCredits}. Pass that id, or the draft video URL. It is the full-resolution route for a draft (upscale_video is for a video that is not a draft); priced like a 1080p render of its length. Paid: call with dryRun first, state the credits, run only when the user agrees. Captions, end card or music added after the draft are not re-applied. Not a draft or expired: a plain refusal; use upscale_video or a fresh 1080p render.

ParameterTypeDescription
draftstringthe draft id from the render result (draft.id)
videostringor: the URL of the 480p draft video
dryRunbooleantrue = return the estimated credits and render nothing

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.

ParameterTypeDescription
videorequiredstringthe source video URL (a render, job result or list_library)
stylestringa look id from hermoso_capabilities (e.g. claymation, knitted-yarn, cel-shaded-anime-cg)
describestringa look in the user’s own words, when no preset fits, or extra detail on top of a preset
styleImagesstring[]pictures that ARE the look (their style, never their subject)
charactersstring[]characters to draw the people as. Using a real person’s face, likeness or voice confirms you are them or have their consent, take full, unlimited responsibility for its use and will cover any claim against Hermoso (hermoso.ai/terms). Paid plan only; recorded.
engine"auto" | "seedance" | "wan" | "wan-prime" | "kling"default auto
resolution"480p" | "720p" | "1080p"Seedance and Wan; default 1080p
faceRoute"face_lane"'face_lane' = the user's "I own the rights to this face" for a real person in the source clip (paid plans). Only on the user's say-so, never on your own.

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.

ParameterTypeDescription
videorequiredstringthe source video URL (a render, job result or list_library)
instructionrequiredstringthe change, in the user’s own words
keepAudiobooleandefault true: keep the source audio; false = silent
lighting"auto" | "preserve" | "relight"default auto: keep the source light unless the change needs new light (night, a lamp, fire). 'preserve' or 'relight' forces it
referencestringOPTIONAL image URL that anchors the MATERIAL of what changes (a fabric, a finish, a colour swatch, the real product). Only its surface is used, never its framing or light
literalbooleantrue = send the instruction exactly as written, with no preserve/lock wrapper
previewFirstFramebooleantrue = edit ONE still of the first frame first and quote the clip; the paid clip does not run
previewStillstringthe still a previewFirstFrame call returned; the clip then matches the changed thing to it
previewAtnumbersecond to preview (default 0); resend with previewStill
elementsobject[]OPTIONAL identity/product grounding (≤4): a creator portrait or the real product photo, so the edit restores the REAL thing. Describe each one in the instruction
interactionIdstringOPTIONAL: the interactionId an earlier Gemini Omni render or edit returned; the edit continues that clip on the same Omni model. If it cannot run, the video editor edits it and the reply says so.
engine"auto" | "seedance" | "kling"default auto: Seedance 2.5 Edit without a real-looking person, else Kling 4.0 Edit
faceRoute"face_lane"'face_lane' = the user's "I own the rights to this face" for a real person in the source clip (paid plans). Only on the user's say-so, never on your own.
textChangesobject[]EVERY change of on-screen words at once ([{from, to}], e.g. each line and its translation). A video laid out from a design is made again with all of them in ONE render, about 2 credits, never a video edit

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 (that is what made the ad work), and any burned-in captions are removed. Pass the source video URL (a previous render, a job result, list_library, or the top performer from post_performance / meta_insights). HOW: the source video itself DRIVES each variant (motion transfer from one image-edited opening frame), so every variant comes back the SAME LENGTH as the source with the same cut, the same performance and the original audio — only the person, outfit, set and props change. Sources up to 30 seconds work as they are; longer ones are refused for free with the way out (trim it first: post_edit with ops [{op:'trim', start:0, end:30}] — clip_video is the AI highlight clipper, not a trim). Returns the plan and ONE JOB PER VARIANT — call get_job on each until it reports done; do not describe a variant before its URL arrives. COST: 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. Your own pictures: 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, X or YouTube post link works as the source: 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 a separate, explicit step — dub_video on a finished variant. `change` is what should change, in the user's own words ('older women', 'winter streets', 'swap the mug for our bottle'): every variant applies it and the variants still differ around it; a part of it about the voice, words, language, music, length or captions cannot change here, and the reply says so and names the tool that can.

ParameterTypeDescription
videorequiredstringthe source video: a file URL, or a TikTok, Reel, Facebook, X or YouTube post link
imagesstring | object[]up to 9 pictures to put in the variants, in the order @Image1, @Image2 name them: URLs or local paths
quality"1080p" | "720p"1080p is the default; 720p costs less, and hands holding a product are less reliable at 720p
useBrandbooleanfalse = send no brand product photo (for a video that is not this workspace brand)
countnumberhow many variants, 1-12 (default 6)
changestringwhat should change, in the user's own words, e.g. 'older women, winter streets'; every variant applies it (omit to let the variants vary freely)
axesstring[]what to vary: character, outfit, location, objects (default all four), or your own, e.g. "season"
notesstringanything the variants must respect, e.g. "keep it women 25-40", "no gyms"
regionsstring[]markets to restyle for, one or more variants each, e.g. ["Berlin","Tokyo","São Paulo"] — visuals only; audio is never translated here
quoteOnlybooleantrue = the exact credits, free; nothing runs
dryRunbooleantrue = the paid plan and the price, no render
planobjectthe `plan` a dryRun returned, rendered without planning again

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

ParameterTypeDescription
videorequiredstringthe finished video to give new hooks: its served file URL
countnumberhow many hook versions, 1-5 (default 3)
notesstringanything the hooks must respect, e.g. "keep it calm", "show the product in every hook"
hookSecondsnumberwhere the CURRENT hook ends, in seconds (1.5-4). Omit to use the first shot cut.
resolution"480p" | "720p" | "1080p"render tier for the new opening. Defaults to the source’s OWN tier so the hook matches the rest of the ad; a lower tier costs a lot less and is scaled into the source’s canvas (visibly softer for the first seconds). dryRun quotes whichever you pick.
productImagestringproduct photo URL used as a reference in hooks that show the product (defaults to the workspace brand’s first product photo)
useBrandbooleanfalse = send no brand name or product photo (for a video that is not this workspace brand’s)
planobjectthe `plan` object a previous dryRun returned, to render exactly those hooks without planning again
quoteOnlybooleantrue = the exact credits, free; nothing runs
dryRunbooleantrue = the paid plan and the price, no render
hooksobject[]your own openings, one version each

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.

ParameterTypeDescription
videorequiredstringthe source video URL
languagerequiredstringtarget language, e.g. 'Spanish', 'de', 'French (Canada)'
scriptstringOPTIONAL override for the original spoken words. Leave this out — the source video is transcribed automatically. Only pass it when you already know the exact script and the auto-transcript got it wrong.
voicestringoptional, e.g. 'Aria' (woman) or 'George' (man): a native voice of that sex speaks. Default: the source speaker's sex.
dryRunbooleanquote only

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.

ParameterTypeDescription
videorequiredstringthe source video URL
voicestringtarget narrator voice preset name, e.g. 'Aria', 'George', 'Rachel', 'Sarah', 'Brian', 'Charlotte' (defaults to a warm female read). A saved VOICE CLONE of the user's own voice counts as a preset here — name it the way it is saved on their cast

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.

ParameterTypeDescription
imagestringwho performs it: the actor/character image URL (@Image1). Required on kling. No one yet: generate_image a portrait of an AI person from words (no reference photo) and pass its url, or a saved / preset creator’s portrait (list_creators). 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.
imagesstring[]seedance/wan: more pictures, @Image2…: a character, product, outfit or place
videorequiredstringthe reference video whose motion to re-perform
promptstringoptional, e.g. 'same moves, new location: Tokyo at night'
engine"auto" | "kling-edit" | "h3" | "seedance" | "wan" | "wan-prime" | "kling"default auto
resolution"480p" | "720p" | "1080p"a person swap defaults to 720p (seedance or kling-edit); otherwise seedance and wan default 1080p; h3 720p or 1080p, default the clip’s own
orientation"video" | "image"kling only: which aspect to keep: the video's (default) or the image's
faceRoute"face_lane"'face_lane' = the user's "I own the rights to this face" for a real person in the source clip (paid plans). Only on the user's say-so, never on your own.
dryRunbooleanreturn the credits this exact job would hold and the engine auto picks, without rendering
voicestringa NEW voice for the new performer: 'match' (a stock voice that fits them), 'female'/'male', or Aria, Sarah, Charlotte, George, Brian, Charlie. Omit = the clip's own sound. Never the original speaker's or a cloned voice
languagestringthe new voice speaks this language (e.g. 'Spanish'), lip-synced to the new words; voice defaults to 'match'
tier"pro" | "standard"kling only: 'pro' (default): 1080p and real hand-object interaction. 'standard': about 25% fewer credits and faster, but 720p, and it tends to mime a held object with empty hands. hermoso_capabilities lists the exact credits for both

recast_hook#

Recast a viral hook with your subject

RECAST A VIRAL HOOK with the brand's own subject (the egg rolling off the table becomes your product; your host does the viral move), then join it into your clip. voice/language: a new voice, or language lip-synced, for one person swapped in. hook: a public post link (TikTok, Reel, Facebook, X, YouTube; one lookup) or a video file. Subject: creator (saved name or id), image (a product or person; a real person's photo records consent), or neither for the brand's first product photo. A DUO OR GROUP (2-4 people): subjects [{creator or image, position: 'left'|'right'|'center' or your own words, refs?, outfit?}], each bound to its place in the frame, keeping that performer's movement and timing on the original sound. LOCATION AND OUTFITS, same pass: location (a place photo or words) moves the scene; outfit (a photo or words) re-dresses the subject, subjects[].outfit each person; with no subject named nobody is swapped and only those change (useBrand:true also swaps in the brand product). Pictures are references, 4 at most; more go in words. Person for person: Seedance 2.5 (720p) if the hook is our render or faceRoute is set, else Wan 3.0 (≤15s; Kling 4.0 Edit past). FULL LENGTH: past its one pass (30 s) the window is recast in passes, each continuing from the previous one's frame and crossfaded across the cut, under the original sound: one video up to 60 s, quoted as passes plus join; the reply names the cut. mode 'auto' is always 'swap' (a region edit: camera, set, light, timing, text and sound kept; a multi-shot hook goes shot by shot); 'motion' only if asked (your person moves in their own photo's setting). swap: your own words for what changes. start/end pick the window (3 s minimum; default: a group or a location/outfit change takes the whole clip, one subject the first 10 s). PAID, QUOTED FIRST: no confirm returns the plan and credits; confirm:true once the user okays it. Then get_job until done, and post_edit with videoUrl = the result and ops [{op:'join', clips:[{url: your clip}], bridge:{kind:'impact'}}].

ParameterTypeDescription
hookrequiredstringthe viral hook: a post link or a video URL
creatorstringa saved creator's name or id (list_creators). NONE SAVED: cast a preset by exact name (list_creators), or pass image instead: a portrait of an AI person that generate_image painted from words (no reference photo) swaps in on every plan, nothing to save. A described lookalike of the user is fine on every plan; a photo of anyone is a real face (paid plan)
imagestringa picture of the subject instead: a product or a person. A person generate_image painted from words with no reference photo counts as an AI face on every plan
subjectsobject[]a duo or group recast, 2-4 people, instead of creator/image
locationstringmove the scene: a photo URL of the place, or words ("a sunny beach at golden hour")
outfitstringwhat the subject (or, with no subject, the main person in the hook) wears instead: an outfit photo URL or words
swapstringwhat changes, in your own words
mode"auto" | "swap" | "motion"auto (always a swap), swap (edit what you describe), or motion (move your person in their own photo's setting)
startnumberwindow start in seconds of the hook; minimum window is 3 seconds
endnumberwindow end in seconds of the hook
promptstringextra direction for the render
modelstringswap: omit for the default; a clip editor id (kling-4-edit, kling-o3-edit; hermoso_capabilities), or for one person swapped for another 'wan-3' (≤15 s) / 'minimax-h3-max-recast'
faceRoute"face_lane"'face_lane' = the user's "I own the rights to this face" for a real person in the source clip (paid plans). Only on the user's say-so, never on your own.
resolution"480p" | "720p" | "1080p"swap: the output tier, on an editor that prices by one (kling-4-edit 720p or 1080p); default the hook's own pixels
tier"pro" | "standard"motion: the motion-transfer tier (default pro)
useBrandbooleanfalse = no brand product photo; true with location/outfit and no subject = also swap in the brand product
voicestringone person swapped in: a NEW voice for them instead of the original speaker's: 'match' (fits them), 'female'/'male', or Aria, Sarah, Charlotte, George, Brian, Charlie. Omit = keep the clip's voice. Never a cloned voice
languagestringthe new voice speaks this language (e.g. 'Spanish'), mouth lip-synced to the new words; voice defaults to 'match'
confirmbooleantrue = render it (after the user okayed the quote)

face_check#

Check a picture or video for a face read-only

Before any paid render: does a picture or video contain a face, and which video models refuse it (they cannot take a real person's face; a render there fails after it starts) or were measured to take one, plus the model a face-bearing render is routed to. A local detector, no model call, 0 credits. It cannot tell a photo from a drawing, so a drawn face counts. Takes a picture or video URL, a Library item or a public post link. A file or Library item is free; a TikTok / Reel / Facebook / X post link is opened through one paid lookup (1 credit), refused when the account has no credits.

ParameterTypeDescription
urlrequiredstringthe picture or video to check

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.

ParameterTypeDescription
brandstring | objectbrand name or profile object; OMIT to use the workspace’s saved brand
productrequiredstringwhat to advertise
countintegerhow many distinct variants (default 6)
languagestringoutput language for the variant copy (e.g. Spanish) — default English

make_static_ads#

Make static ads at scale

Make a BATCH of finished static image ads for one or several products in one call: each ad a genuinely different selling angle (offer, material or ingredient story, how it is used, a lifestyle moment, an objection, proof the brand really has) AND a different layout (editorial still life, poster headline, packshot on colour, in hand, flat lay, split compare, native phone photo and more; never one look repeated across a batch), with its headline, supporting line and CTA printed on the image and the product's real photo and the brand's real logo composited in. `products` names products from the brand's product catalog by id or name (list_products; add one from its page link with add_product; ['all'] takes every product with a photo; omit for the hero product). A product's price is printed only when it was read from its live page within the last hour: older rows are re-read first, and a page that cannot be read means no price on that ad. `count` is ads PER PRODUCT (1-20). `aspectRatios` any of 1:1, 4:5, 9:16 (each ad is composed natively once per ratio, so the ratios are different pictures with the same words; for one picture in several sizes render one ratio and use resize_ad). `brief` steers (a sale, a season, an audience) and is never turned into a price, discount or claim the brand does not state; `angles` names the angles to spread across; `layouts` (free words) replaces the layout set. ALWAYS PRICED FIRST: dryRun:true returns the plan and the quote; then pass that `plan` (and its `batchId`) back to render exactly it without planning again. Renders are QUEUED jobs, at most 12 per call: the rest comes back as `remaining`, to send again as `plan` once some finish. Read each job with get_job; never describe an ad before its URL arrives. For variants of ONE finished ad use headline_variants / resize_ad; to copy a competitor's static use clone_static.

ParameterTypeDescription
productsstring[]product ids or names from list_products, ['all'] for every product with a photo, or omit for the hero product
countintegerads per product, 1-20 (default 4)
aspectRatios"1:1" | "4:5" | "9:16"[]canvases; every ad is rendered once per ratio (default ['4:5'])
briefstringwhat to lean into: a sale the brand is really running, a season, an audience, a tone
anglesstring[]the selling angles to spread across, e.g. ['price or offer', 'material story', 'how it is used', 'lifestyle moment']; omit and the planner picks a spread
layoutsstring[]your own layout descriptions in words, used slot by slot instead of the built-in layout set
languagestringlanguage of the on-image copy (default English)
modelstringimage model id from hermoso_capabilities; omit for the default
brandIdstringa profile id/name from list_brands; omit for the active profile (passing it pins it like use_brand)
planobject[]the `plan` (or `remaining`) a previous call returned: render exactly those ads, no new planning
batchIdstringthe batchId a previous call returned, so the rest lands in the same batch
dryRunbooleantrue = plan and quote only, nothing rendered
logoPlacement"auto" | "overlay" | "in_scene" | "none"overlay (default) | in_scene (real file printed onto a blank surface) | none | auto

make_listing_set#

Make a marketplace listing set

Make a marketplace LISTING SET for ONE product in one call (Amazon, Shopify, Etsy, any store page): the MAIN image on pure white (the product alone filling about 85% of the frame, no text, no props, no added logo), then the secondary images a listing carries — a lifestyle shot, a detail close-up with NO infographic, a scale shot that shows its true size, and the product in use — every one rendered from the product's real photo and held to one visual system (the same light, scale and palette). More shots on request: multi_angle, whats_in_box, back_of_pack, or any shot in your own words. The MAIN renders FIRST and every other image is drawn from it (each waits for it in the queue), so the set matches; pass an approved main as mainImage to redo the rest from it. aplus:true adds Amazon's 7 A+ modules (a 21:9 brand hero and brand promise, 3:2 modules 2-6), each also delivered at its Amazon module's exact pixels, with its text fields suggested only from the product's own data. Priced up front: dryRun:true returns the plan and the exact credits; the charge is that number. Each image is its own queued image job (poll with get_job); they land in the Library. For a batch of ADS use make_static_ads; for one product photo in a scene use plan_ad + generate_image.

ParameterTypeDescription
productstringthe product name or id from list_products: its catalog photo and its own facts are used
imagestringthe product photo when it is not in the catalog: an https or upload_file URL, a Library id (L0, L1…) or a local path on the CLI
titlestringthe product name, when it is not in the catalog
descriptionstringwhat the product is, when it is not in the catalog
inBoxstring[]what ships in the box (for a whats_in_box shot); nothing else is ever drawn in it
shotsstring[]the images, in order: built-in main, lifestyle, detail, scale, in_use, multi_angle, whats_in_box, back_of_pack, or any shot in your own words. Default: main, lifestyle, detail, scale, in_use. main always comes first when asked
aspectRatio"1:1" | "4:5" | "3:4" | "4:3" | "16:9" | "9:16" | "2:3" | "3:2"the canvas for the whole set; default 1:1 (the marketplaces' square)
briefstringthe seller's direction for the whole set: a mood, a setting, an audience
modelstringimage model id from hermoso_capabilities; omit for the default
brandIdstringa profile id/name from list_brands; omit for the active profile (passing it pins it like use_brand)
aplusbooleanalso Amazon's 7 A+ modules: 1 brand hero and 7 brand promise at 21:9 (Premium full-width 1464x600 + mobile 600x450), 2-6 at 3:2 (key benefits, how to use, materials and details, lifestyle, what you get; Premium image-with-text 800x600, Basic header 970x600). With shots:[] only the A+ modules
copyobjectthe seller's own words for A+ text fields, by module id then field, e.g. {aplus_hero:{headline:'…'}}; each is clipped to Amazon's limit. Without it the fields are suggested only from the product's own data, never invented
copyIn"fields" | "image"A+ words: 'fields' (default) = suggested for Amazon's text fields, none painted; 'image' = the words painted on the picture
mainImagestringan APPROVED main image (URL or Library id) to draw every other image from instead of rendering a new main
mainJobstringthe mainJob id a previous call returned, when rendering its `remaining`: they are drawn from that main
planobject[]the `plan` (or `remaining`) a previous call returned: render exactly those images, no new plan, with the same image
dryRunbooleantrue = plan and quote only, nothing rendered or charged

list_products#

List the product catalog

The brand's product catalog: each product's id, title, page link, price (and whether it is current), photos, its own selling points and audience, and when it was last checked against its live page. Free. `refresh:'stale'` re-reads rows older than a day first, `refresh:'all'` re-reads every page. Use the ids with make_static_ads.

ParameterTypeDescription
refresh"stale" | "all"re-read product pages before listing: 'stale' (older than a day) or 'all'
brandIdstringa profile id/name from list_brands; omit for the active profile

add_product#

Add a product from a link destructive

Add a product to the brand's catalog from its product-page link: reads the page's own data (name, every photo, price and currency, description, its bullet-point selling points), no model, 0 credits. Re-adding a link updates the row (the live page wins). Without a url, `title` adds one by hand. `audience` and `sellingPoints` you pass are kept through later re-reads.

ParameterTypeDescription
urlstringthe product page link
titlestringto add a product by hand (no link)
imagesstring[]photo urls for a hand-added product
pricestringhand-added only; never printed as current unless read from a page
currencystringcurrency code for the price, e.g. USD
descriptionstringproduct description
sellingPointsstring[]bullet-point selling points, one per item
audiencestringwho this product is for
brandIdstringa profile id/name from list_brands; omit for the active profile

refresh_products#

Re-check products against their pages destructive

Re-read product pages now (all products with a page, or `ids`) and update the catalog: the live page wins, a failed read is recorded and said. `sync:true` instead imports the whole store catalog (the brand's connected Shopify store, else its website's public catalog), merging into existing rows. Free.

ParameterTypeDescription
idsstring[]product ids to re-read; omit to re-read every product that has a page
syncbooleanimport the store catalog instead of re-reading pages
brandIdstringa profile id/name from list_brands; omit for the active profile

update_product#

Edit a product destructive

Edit a catalog product's title, description, selling points, audience or page link. What you set here is kept through later page re-reads. Free.

ParameterTypeDescription
idrequiredstringcatalog product id, as add_product returned it
titlestringproduct name
descriptionstringproduct description
sellingPointsstring[]selling points; replaces the saved list
audiencestringwho this product is for
urlstringproduct page link
brandIdstringa profile id/name from list_brands; omit for the active profile

remove_product#

Remove a product destructive

Remove one product from the brand's catalog (its ads already made stay in the Library). Free.

ParameterTypeDescription
idrequiredstringcatalog product id to remove
brandIdstringa profile id/name from list_brands; omit for the active profile

product-photo tools

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

ParameterTypeDescription
brandIdstringa profile id/name from list_brands whose product library to list; omit to use the active profile

set_product_image#

Set product photo destructive

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

ParameterTypeDescription
imageUrlrequiredstringthe image URL to lock as the product (from a research result, a workspace / list_product_photos url, or any public product photo)
source_notestringa short note on where it came from, e.g. "from their IG post"
brandIdstringa profile id/name from list_brands to lock the product for; omit to use the active profile

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.

ParameterTypeDescription
appNamestringthe app's name to look up on the App Store — defaults to the saved brand's name
storeUrlstringa Google Play / App Store link or package id
brandIdstringa profile id/name from list_brands to save the screens onto; omit to use the active profile