이 MCP로 할 수 있는 일
Supports ad research, competitor analysis, video and static ad creation, campaign policy checks, content publishing, and performance tracking.
도구
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['video'], 'properties': {'auto': {'enum': ['sentences', 'words'], 'type': 'string'}, 'burn': {'type': 'boolean', 'description': 'false = only the .srt'}, 'cues': {'type': 'array', 'items': {'type': 'object', 'required': ['text', 'start', 'end'], 'properties': {'end': {'type': 'number'}, 'text': {'type': 'string'}, 'start': {'type': 'number'}}}, 'maxItems': 400, 'description': 'your own lines in seconds, no overlap, max 90 chars, no emoji'}, 'video': {'type': 'string', 'description': 'the video to subtitle'}, 'textStyle': {'anyOf': [{'type': 'string'}, {'type': 'object', 'properties': {'font': {'type': 'string', 'description': 'sans|serif|elegant|condensed|hand or any Google Fonts family'}, 'size': {'anyOf': [{'type': 'string'}, {'type': 'number'}], 'description': "s|m|l|xl, '120px', or a 0.015-0.15 frame fraction"}, 'tilt': {'type': 'number', 'description': 'degrees, ±45'}, 'color': {'type': 'string', 'description': 'any CSS colour'}, 'italic': {'type': 'boolean'}, 'preset': {'type': 'string'}, 'shadow': {'type': 'boolean'}, 'weight': {'type': 'number'}, 'outline': {'type': 'boolean'}, 'subFont': {'type': 'string'}, 'describe': {'type': 'string', 'description': 'the look in words'}, 'position': {'anyOf': [{'type': 'string'}, {'type': 'number'}], 'description': 'top|center|lower|bottom or 0.05-0.95 from the top'}, 'textCase': {'enum': ['as-is', 'upper', 'lower', 'title'], 'type': 'string'}, 'cardColor': {'type': 'string'}, 'subItalic': {'type': 'boolean'}, 'background': {'type': 'string', 'description': 'none|pill|a colour'}, 'outlineColor': {'type': 'string'}}}], 'description': 'the look: words, a preset or fields; omit for the default'}, 'wordsPerCue': {'type': 'number'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['url'], 'properties': {'url': {'type': 'string', 'description': 'the video URL (a served /generated/ path or a public http(s) video)'}, 'words': {'description': "true | 'only': each spoken word's start/end"}, 'frames': {'type': 'boolean', 'description': 'also return the frames as images'}, 'anchors': {'type': 'array', 'items': {}, 'description': '{afterWord|beforeWord|atWord, occurrence?, offset?} -> s'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['documentId', 'text'], 'properties': {'text': {'type': 'string', 'description': 'text to append at the end of the doc'}, 'documentId': {'type': 'string', 'description': 'the document id from create_doc'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['spreadsheetId', 'rows'], 'properties': {'rows': {'type': 'array', 'items': {'type': 'array', 'items': {'anyOf': [{'type': 'string'}, {'type': 'number'}, {'type': 'boolean'}]}}, 'description': 'rows to append — array of row arrays'}, 'range': {'type': 'string', 'description': 'range to append at (default A1 / first sheet)'}, 'spreadsheetId': {'type': 'string', 'description': 'the spreadsheet id from create_sheet'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['channel'], 'properties': {'limit': {'type': 'number', 'description': 'how many posts this page (default 50, max 200)'}, 'cursor': {'type': 'string', 'description': 'resume from a previous run'}, 'channel': {'enum': ['facebook', 'instagram', 'threads', 'youtube', 'tiktok', 'pinterest', 'bluesky'], 'type': 'string', 'description': 'which channel to import from'}, 'confirm': {'type': 'boolean', 'description': 'actually import — omit for a dry run that only quotes the cost'}, 'accountRef': {'type': 'string', 'description': 'which Page / account, when the brand has more than one'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'pack': {'type': 'string', 'description': 'the pack id to buy (e.g. pack-2k) — omit to list the available packs first'}, 'confirm': {'type': 'boolean', 'description': 'set true to actually charge the saved card for `pack` (required for the one-click charge; ignored on the checkout-link path)'}, 'quote_token': {'type': 'string', 'description': 'the quoteToken returned by the quote step — REQUIRED (with confirm:true) to charge; it binds the exact pack + price you quoted (10-minute validity) and makes a retried confirm idempotent'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['name'], 'properties': {'args': {'type': 'object', 'description': "the tool's arguments as an object, exactly as its own schema takes them", 'propertyNames': {'type': 'string'}, 'additionalProperties': {}}, 'name': {'type': 'string', 'description': 'the tool name exactly as find_tools returned it, e.g. create_meta_lead_form'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['id'], 'properties': {'id': {'type': 'string', 'description': 'the scheduled post id from list_scheduled'}, 'brand': {'type': 'string', 'description': 'WHICH BRAND this post is in — id or exact name from list_brands; needed when it is not the brand this connection is pinned to. A name that matches no brand, or two, is REFUSED.'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['video'], 'properties': {'video': {'type': 'string', 'description': 'the source video URL'}, 'voice': {'type': 'string', 'description': "target narrator voice preset name, e.g. 'Aria', 'George', 'Rachel', 'Sarah', 'Brian', 'Charlotte' (defaults to a warm female read). A saved VOICE CLONE of the user's own voice counts as a preset here — name it the way it is saved on their cast"}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['copy'], 'properties': {'copy': {'type': 'string', 'description': 'the ad copy / script / on-screen text to check'}, 'claims': {'type': 'string', 'description': 'the claims / proof points the ad makes'}, 'category': {'type': 'string', 'description': 'the product category — helps pick the relevant policy pages'}, 'imageDescription': {'type': 'string', 'description': 'a description of the creative / image when relevant'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['range'], 'properties': {'range': {'type': 'string', 'description': 'the range to clear, e.g. "A2:D50" or "Sheet1!A2:D50"'}, 'confirm': {'type': 'boolean'}, 'sheetUrl': {'type': 'string'}, 'confirmCells': {'type': 'number', 'description': 'echo back the filled-cell count the unconfirmed call reported'}, 'spreadsheetId': {'type': 'string'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['video'], 'properties': {'count': {'type': 'number', 'description': 'how many clips to cut, 1-8 (default 4)'}, 'video': {'type': 'string', 'description': 'the long video to clip — a YouTube/Vimeo/Loom/Dailymotion/Streamable/Rumble/Wistia/Twitch/TED watch URL, a direct https .mp4/.mov/.webm, or a Hermoso /generated/ URL'}, 'captions': {'type': 'boolean', 'description': 'burn subtitles into every clip. DEFAULT TRUE — a clip cut from a podcast or a talk is watched on mute, and the words are the product. Set false for clean footage. A clip whose window carries no readable speech is delivered bare rather than captioned with a guess, and the result says which.'}, 'aspectRatio': {'type': 'string', 'description': "clip shape: '9:16' (default), '1:1', '16:9', any 'W:H', or 'keep' for the source framing"}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['imageUrl'], 'properties': {'brandId': {'type': 'string', 'description': 'a brand id/name from list_brands to clone for; omit to use the active brand'}, 'imageUrl': {'type': 'string', 'description': 'the URL of the static ad image to clone. Using a real person’s face, likeness or voice confirms you are them or have their consent, take full, unlimited responsibility for its use and will cover any claim against Hermoso (hermoso.ai/terms). Paid plan only; recorded.'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['url'], 'properties': {'url': {'type': 'string', 'description': 'the video to clone — a TikTok / Instagram Reel / Facebook / X / YouTube link, or a direct https video file URL'}, 'brand': {'anyOf': [{'type': 'string'}, {'type': 'object', 'properties': {}, 'additionalProperties': {}}], 'description': "brand name or profile object; OMIT to use the workspace's saved brand (see get_brand)"}, 'changes': {'type': 'string', 'description': 'what to change or keep from the original, in the user\'s words (e.g. "same hook but in a gym", "keep the jump cut, older creator")'}, 'product': {'type': 'string', 'description': "what the new ad sells, plus any angle or offer; omit to use the saved brand's product"}, 'language': {'type': 'string', 'description': "language for the new ad's script and copy — default English"}, 'durationSeconds': {'type': 'number', 'description': 'override the length in seconds; omit to match the original'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'max': {'type': 'number', 'description': 'cap how many posts to read in this run (default 40)'}, 'brand': {'type': 'string', 'description': 'WHICH BRAND the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a brand this connection is not pinned to; applies to THIS CALL ONLY. A name that matches no brand, or two, is REFUSED and nothing is done.'}, 'remeasure': {'type': 'boolean', 'description': 'ALSO re-read posts older than 7 days whose every reading came back empty or failed — use after post_performance reports posts "read but empty", or once a channel\'s reader has been fixed. Otherwise those windows stay closed.'}, 'includeMetered': {'type': 'boolean', 'description': 'also read X, which BILLS CREDITS per post read — ask the user first'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['competitor'], 'properties': {'ads': {'type': 'array', 'items': {'type': 'object', 'properties': {}, 'additionalProperties': {}}, 'description': 'ad objects to tear down (from pull_competitor_ads / search_meta_ads). Omit to auto-pull their Meta ads first.'}, 'language': {'type': 'string', 'description': 'output language (default English)'}, 'competitor': {'type': 'object', 'required': ['name'], 'properties': {'name': {'type': 'string', 'description': 'the competitor brand name'}, 'domain': {'type': 'string', 'description': 'their domain — sharpens the auto-pull page match'}}, 'description': 'the competitor to tear down'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['provider'], 'properties': {'fields': {'type': 'object', 'description': 'that provider\'s own field names and values, e.g. {"apiKey":"…"}; the names for each provider are in the description', 'propertyNames': {'type': 'string'}, 'additionalProperties': {'type': 'string'}}, 'provider': {'type': 'string', 'description': 'the connector id: stripe, openai_ads, apple_ads, bluesky, telegram, bing_webmaster, posthog, mixpanel, amplitude, slack, discord, webhook'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['fileId'], 'properties': {'width': {'type': 'number', 'description': 'REQUIRED for jpg — output width in pixels'}, 'fileId': {'type': 'string', 'description': 'the OneDrive item id, from list_onedrive_files'}, 'format': {'enum': ['pdf', 'jpg'], 'type': 'string', 'description': 'default pdf'}, 'height': {'type': 'number', 'description': 'REQUIRED for jpg — output height in pixels'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['name'], 'properties': {'name': {'type': 'string', 'description': 'the brand / client name for the new workspace'}, 'activate': {'type': 'boolean', 'description': 'switch this connection to the new brand (default true) — everything you do next scopes to it'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'text': {'type': 'string', 'description': 'body text to insert'}, 'title': {'type': 'string', 'description': 'document title'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['name'], 'properties': {'name': {'type': 'string', 'description': 'folder name'}, 'parentId': {'type': 'string', 'description': 'parent folder id for a nested folder (default: Drive root)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['name'], 'properties': {'name': {'type': 'string', 'description': 'folder name'}, 'parentId': {'type': 'string', 'description': 'parent folder id for a nested folder (default: OneDrive root)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'rows': {'type': 'array', 'items': {'type': 'array', 'items': {'anyOf': [{'type': 'string'}, {'type': 'number'}, {'type': 'boolean'}]}}, 'description': 'rows to write — array of row arrays; first row = headers'}, 'title': {'type': 'string', 'description': 'spreadsheet title'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['brand'], 'properties': {'brand': {'type': 'string', 'description': 'brand id or exact name from list_brands'}, 'confirm': {'type': 'boolean', 'description': 'REQUIRED true — this destroys the whole workspace and cannot be undone'}, 'confirmName': {'type': 'string', 'description': "the workspace's EXACT name, required when it is not empty — copy it from the inventory this tool returned, after the user has agreed to it"}, 'confirmConnectors': {'type': 'number', 'description': 'the number of connected accounts the inventory reported, required when there is at least one — the user must specifically agree to losing them, because reconnecting each needs a browser and no agent can do it'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['id'], 'properties': {'id': {'type': 'string', 'description': 'the creator id (from list_creators)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['fileId'], 'properties': {'fileId': {'type': 'string', 'description': 'the Drive file id'}, 'confirm': {'type': 'boolean', 'description': 'REQUIRED true'}, 'permanent': {'type': 'boolean', 'description': 'true = delete forever; default trashes (recoverable)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['subscriptionId'], 'properties': {'pageId': {'type': 'string', 'description': 'the company Page that owns the form — from list_linkedin_pages; omit when one Page is shared'}, 'adAccountId': {'type': 'string', 'description': 'read forms owned by an AD ACCOUNT instead of a Page'}, 'subscriptionId': {'type': 'string'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['fileId'], 'properties': {'fileId': {'type': 'string', 'description': 'the OneDrive item id'}, 'confirm': {'type': 'boolean', 'description': 'REQUIRED true'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['id'], 'properties': {'id': {'type': 'string', 'description': 'the playbook id (from list_playbooks)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['id'], 'properties': {'id': {'type': 'string', 'description': 'the custom skill id (from list_skills)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'limit': {'type': 'number', 'description': 'how many recent posts to diagnose (default 25, max 200). The baseline is always built from EVERY post recorded for the brand, never only these, so a bad month can never become its own definition of normal.'}, 'channel': {'type': 'string', 'description': 'restrict to one channel: facebook, instagram, threads, x, linkedin, youtube, tiktok, reddit, pinterest'}, 'converting': {'type': 'boolean', 'description': 'pass false ONLY when the user has told you these posts are getting seen and are not converting — it re-reads the ones that are earning their reach as an offer problem instead of a win. Omit when you do not know; we cannot measure it.'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['provider'], 'properties': {'account': {'type': 'string', 'description': 'on a channel with several connected accounts (TikTok, X, YouTube, Threads, Bluesky, Telegram, Reddit, Pinterest): remove ONLY this account (@handle or id from list_connector_accounts) and keep the others'}, 'confirm': {'type': 'boolean', 'description': "REQUIRED true — reconnecting a sign-in account afterwards needs the user's browser; a paste-a-key account is reconnected with connect_connector"}, 'provider': {'type': 'string', 'description': 'provider id exactly as list_connectors reports it, e.g. "meta", "google_ads", "youtube", "linkedin"'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'save': {'type': 'boolean', 'description': 'save as the workspace’s brand (like Studio onboarding) so plan_ad/create use it automatically. Default: saves only when NO brand is saved yet; pass true to REPLACE the saved brand profile (the drafted fields overwrite the saved ones), false to never save'}, 'domain': {'type': 'string', 'description': 'a website to scrape'}, 'platform': {'type': 'string', 'description': 'platform for socialHandle (instagram/tiktok/…)'}, 'description': {'type': 'string', 'description': 'a free-text brand description (no website)'}, 'socialHandle': {'type': 'string', 'description': 'a social handle to draft from (influencers/creators) — pair with platform'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['video', 'language'], 'properties': {'video': {'type': 'string', 'description': 'the source video URL'}, 'voice': {'type': 'string', 'description': "optional target voice preset, e.g. 'Aria' (warm female) or 'George' (confident male). Defaults to a voice matching the source speaker's register."}, 'script': {'type': 'string', 'description': 'OPTIONAL override for the original spoken words. Leave this out — the source video is transcribed automatically. Only pass it when you already know the exact script and the auto-transcript got it wrong.'}, 'language': {'type': 'string', 'description': "target language, e.g. 'Spanish', 'de', 'French (Canada)'"}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['id'], 'properties': {'at': {'type': 'string', 'description': 'when the copy goes out — ISO timestamp or epoch milliseconds (default: an hour from now)'}, 'id': {'type': 'string', 'description': 'the post to copy, from list_scheduled'}, 'link': {'type': 'string'}, 'brand': {'type': 'string', 'description': 'WHICH BRAND this post is in — id or exact name from list_brands; needed when it is not the brand this connection is pinned to. A name that matches no brand, or two, is REFUSED.'}, 'title': {'type': 'string'}, 'chatId': {'type': 'string', 'description': 'TELEGRAM — which chat, group or channel the copy goes to (@username or numeric id)'}, 'ideaId': {'type': 'string', 'description': 'short id of the content-plan idea this post came from'}, 'pageId': {'type': 'string', 'description': 'FACEBOOK / INSTAGRAM / THREADS — which connected Page (list_meta_pages)'}, 'recipe': {'type': 'string', 'description': 'the post\'s FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format'}, 'boardId': {'type': 'string', 'description': 'PINTEREST — the board for the copy (list_pinterest_boards)'}, 'message': {'type': 'string', 'description': 'a different caption for the copy'}, 'captions': {'type': 'object', 'description': 'per-channel caption overrides for the copy', 'propertyNames': {'type': 'string'}, 'additionalProperties': {'type': 'string'}}, 'channels': {'type': 'array', 'items': {'enum': ['facebook', 'instagram', 'threads', 'tiktok', 'youtube', 'linkedin', 'x', 'pinterest', 'google_business', 'bluesky', 'telegram'], 'type': 'string'}, 'description': 'post the copy to these channels instead of the original’s'}, 'imageUrl': {'type': 'string'}, 'timezone': {'type': 'string', 'description': 'IANA zone for the queue, e.g. "America/New_York"'}, 'useQueue': {'type': 'boolean', 'description': 'instead of naming a time, take the brand’s next free posting slot'}, 'videoUrl': {'type': 'string'}, 'imageUrls': {'type': 'array', 'items': {'type': 'string'}, 'description': 'CAROUSEL — an ORDERED list of image URLs published as ONE swipeable post'}, 'locationId': {'type': 'string', 'description': 'GOOGLE BUSINESS — which listing (list_business_locations)'}, 'visibility': {'enum': ['public', 'unlisted', 'private', 'draft'], 'type': 'string'}, 'linkedinOrganizationId': {'type': 'string', 'description': 'LINKEDIN — publish the copy as this company Page (list_linkedin_pages)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['image', 'instruction'], 'properties': {'mask': {'type': 'string', 'description': 'optional mask image (URL or local path) marking the region to change: transparent = change, or white = change on an opaque mask'}, 'image': {'type': 'string', 'description': 'the image to edit: URL, Library item URL, upload_file URL or local path'}, 'dryRun': {'type': 'boolean', 'description': 'true = return the exact credits this edit reserves and render nothing'}, 'removal': {'type': 'boolean', 'description': 'true when the edit REMOVES text, branding, a logo, a watermark, a person or an object, so the brand name and logo are not re-added'}, 'fixLabel': {'type': 'boolean', 'description': 'false = leave the product label exactly as rendered: no product lookup, no label check, no re-print (default on when the ad shows the brand’s saved product)'}, 'instruction': {'type': 'string', 'description': 'the change to make, in plain words (pass the user’s own words for a removal or plain photo edit)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['video', 'instruction'], 'properties': {'video': {'type': 'string', 'description': 'the source video URL (a render, job result or list_library)'}, 'literal': {'type': 'boolean', 'description': 'true = send the instruction exactly as written, with no preserve/lock wrapper'}, 'elements': {'type': 'array', 'items': {'type': 'object', 'required': ['frontal'], 'properties': {'refs': {'type': 'array', 'items': {'type': 'string'}, 'description': 'up to 2 extra angles of the SAME subject'}, 'frontal': {'type': 'string', 'description': 'the reference image URL'}}, 'additionalProperties': {}}, 'description': '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'}, 'lighting': {'enum': ['auto', 'preserve', 'relight'], 'type': 'string', 'description': "default auto: keep the source light unless the change needs new light (night, a lamp, fire). 'preserve' or 'relight' forces it"}, 'keepAudio': {'type': 'boolean', 'description': 'default true: keep the source audio; false = silent'}, 'previewAt': {'type': 'number', 'description': 'second to preview (default 0); resend with previewStill'}, 'reference': {'type': 'string', 'description': 'OPTIONAL image URL that anchors the MATERIAL of what changes (a fabric, a finish, a colour swatch, the real product). Only its surface is used, never its framing or light'}, 'instruction': {'type': 'string', 'description': 'the change, in the user’s own words'}, 'previewStill': {'type': 'string', 'description': 'the still a previewFirstFrame call returned; the clip then matches the changed thing to it'}, 'interactionId': {'type': 'string', 'description': 'OPTIONAL: the interactionId an earlier Gemini Omni render or edit returned; the edit continues that clip on the same Omni model. If it cannot run, the video editor edits it and the reply says so.'}, 'previewFirstFrame': {'type': 'boolean', 'description': 'true = edit ONE still of the first frame first and quote the clip; the paid clip does not run'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['groups'], 'properties': {'groups': {'type': 'array', 'items': {'type': 'string'}, 'description': "Groups to switch on, e.g. ['ads']. Unknown names are refused by name rather than ignored."}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['fingerprint'], 'properties': {'fingerprint': {'type': 'string', 'description': 'the `fp` value from list_errors'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'limit': {'type': 'number', 'description': 'max ads to include, 1-60 (default 30)'}, 'title': {'type': 'string', 'description': 'deck title (default: the collection name)'}, 'collection': {'type': 'string', 'description': 'the swipefile collection to export, by name or id (default: the first collection)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'appName': {'type': 'string', 'description': "the app's name to look up on the App Store — defaults to the saved brand's name"}, 'brandId': {'type': 'string', 'description': 'a brand id/name from list_brands to save the screens onto; omit to use the active brand'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['url'], 'properties': {'url': {'type': 'string', 'description': 'the asset url or /generated/ path'}, 'name': {'type': 'string', 'description': 'optional filename for the download'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['path'], 'properties': {'path': {'type': 'string', 'description': "exact endpoint path, e.g. '/v1/tiktok/profile' — non-allowlisted paths are rejected"}, 'params': {'type': 'object', 'properties': {}, 'description': "endpoint query params, e.g. {handle:'nike'}", 'additionalProperties': {}}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['domain'], 'properties': {'mode': {'enum': ['competitors', 'inspiration', 'company'], 'type': 'string', 'description': "'competitors' (default, excludes the searched company), 'inspiration' (best relevant ads incl. it), or 'company'"}, 'domain': {'type': 'string', 'description': 'the brand domain, e.g. flourish.com'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['niche'], 'properties': {'limit': {'type': 'number', 'description': 'creators to return, 1–30 (default 12)'}, 'niche': {'type': 'string', 'description': 'product category, topic or hashtag — "calorie tracker app", "matcha", "#cleanbeauty"'}, 'enrich': {'type': 'boolean', 'description': 'read follower counts for the top 6 (default true, ~1 credit each)'}, 'queries': {'type': 'number', 'description': 'query variants per platform, 1–4 (default 3); each is a paid search call'}, 'platforms': {'type': 'array', 'items': {'enum': ['tiktok', 'instagram', 'youtube'], 'type': 'string'}, 'description': 'default all three'}, 'marketplace': {'type': 'boolean', 'description': 'also search Instagram’s creator marketplace (Meta’s own creator directory, free) for the same niche; needs the Meta connector'}, 'minAvgViews': {'type': 'number'}, 'minEngagement': {'type': 'number', 'description': 'interactions per view, 0–1 (0.05 = 5%)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'url': {'type': 'string'}, 'pick': {'type': 'number'}, 'query': {'type': 'string'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'group': {'type': 'string', 'description': 'limit to one group: core, research, create, channels, channel_admin, analytics, ads, files, workspace'}, 'limit': {'type': 'number', 'description': 'how many to return (default 12, max 40)'}, 'query': {'type': 'string', 'description': 'words from the task or the tool name, e.g. "lead form", "whatsapp", "google ads keyword", "meta insights"'}, 'onlyHealthy': {'type': 'boolean', 'description': 'leave out tools that are failing their recent calls or that need a connector this workspace has not made. Default false — nothing is hidden unless you ask, because a missing row reads as a missing capability.'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['videoUrl'], 'properties': {'sub': {'type': 'string', 'description': 'accent sub-pill copy, ≤34 chars (usually the product/brand)'}, 'grain': {'type': 'boolean', 'description': 'default false — anti-AI film-grain finish'}, 'pills': {'type': 'boolean', 'description': 'default true — set false for a grain-only pass'}, 'accent': {'type': 'string', 'description': 'brand accent hex for the sub-pill'}, 'header': {'type': 'string', 'description': 'header pill copy, ≤40 chars (required when pills is on)'}, 'points': {'type': 'array', 'items': {'type': 'string'}, 'description': '3-4 proof points, ≤44 chars each'}, 'videoUrl': {'type': 'string', 'description': 'the served URL of the video to finish (from a previous render/job)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['videoUrl', 'startSeconds', 'endSeconds', 'prompt'], 'properties': {'dryRun': {'type': 'boolean', 'description': 'true = return the exact credits this fix reserves and render nothing'}, 'prompt': {'type': 'string', 'description': "what the replacement footage should show — describe the shot, matching the master's style"}, 'refImage': {'type': 'string', 'description': 'optional product/style anchor image URL'}, 'videoUrl': {'type': 'string', 'description': 'the served URL of the master video to fix'}, 'endSeconds': {'type': 'number', 'description': 'window end in seconds (window 1.5-8s)'}, 'startSeconds': {'type': 'number', 'description': 'window start in seconds'}, 'speechWindows': {'type': 'array', 'items': {'type': 'array', 'items': {'type': 'number'}}, 'description': '[[start,end],...] windows with spoken lines — the fix window must not overlap these'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['id'], 'properties': {'id': {'type': 'string', 'description': 'the memory item id (from list_memory)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'tab': {'type': 'string', 'description': 'tab title or numeric sheetId (default: the first tab)'}, 'sheetUrl': {'type': 'string'}, 'autoResize': {'type': 'boolean'}, 'boldHeader': {'type': 'boolean'}, 'freezeRows': {'type': 'number', 'description': 'how many top rows to freeze (default 1, 0 = none)'}, 'spreadsheetId': {'type': 'string'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['image', 'script'], 'properties': {'seed': {'type': 'integer', 'maximum': 9007199254740991, 'minimum': -9007199254740991, 'description': 'fixed seed, only for an engine other than standard'}, 'image': {'type': 'string', 'description': 'local path or URL of the presenter portrait. Using a real person’s face, likeness or voice confirms you are them or have their consent, take full, unlimited responsibility for its use and will cover any claim against Hermoso (hermoso.ai/terms). Paid plan only; recorded.'}, 'voice': {'type': 'string', 'description': 'voice name (Aria / Sarah / George / Adam). Leave it out and the voice matches the person in `image`; if nobody can be read, the call is refused free asking for one'}, 'dryRun': {'type': 'boolean', 'description': 'return the credits this exact job would hold, without rendering'}, 'engine': {'type': 'string', 'description': 'leave out for the standard engine; only an engine listed in hermoso_capabilities avatarEngines is accepted'}, 'prompt': {'type': 'string', 'description': 'motion direction, only for an engine other than standard that hermoso_capabilities lists'}, 'script': {'type': 'string', 'description': 'the words the avatar speaks'}, 'resolution': {'type': 'string', 'description': "'720p' (default) or '480p'"}, 'acceptQueue': {'type': 'boolean', 'description': 'only for an engine hermoso_capabilities marks oneAtATime: wait in line'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'raw': {'type': 'boolean', 'description': 'RAW MODEL ACCESS: run the caller’s prompt on the named model with no Hermoso adjustments at all — the prompt reaches the provider byte-identical (no hex-to-colour-name rewrite, no prepended fidelity preamble) and NO saved-brand product photos are attached, so the model you name is the model that renders. Use it to drive the raw catalog; leave it off for an on-brand ad. Billing, the durable Library landing and per-model validation are unchanged.'}, 'mask': {'type': 'string', 'description': "MASKED EDIT — change ONE region of an image and keep the rest: a local path or URL of a mask image for refImages[0] (the image being edited). Either convention works and the reply says which it read: TRANSPARENT pixels = change, or, on a mask with no transparency, WHITE = change and black = keep. Any size; it is scaled to the image. The mask GUIDES the edit rather than stencilling it: the new content can blend a little past its edge. Runs on the model hermoso_capabilities marks `refs.mask` (gpt-image-2.5): leave `model` empty or name that one — any other named model is refused, free. Needs refImages; the result keeps the source image's own frame, so aspectRatio is not applied."}, 'model': {'type': 'string', 'description': 'image model id from hermoso_capabilities. A model whose `refs.mode` is "edit" there (gpt-image-2.5) takes your refImages on ITS OWN editor, up to its `refs.max`, instead of the default compositor'}, 'prompt': {'type': 'string', 'description': 'REQUIRED on every model EXCEPT the pose rows below. the full image prompt — subject, composition, lighting, and any on-image ad text. ON A POSE MODEL (product-in-hand / try-on) THIS IS EXTRA DIRECTION AND IT IS OPTIONAL — leave it out and the pose is built for you. If you do write one, DESCRIBE THE POSE ("she holds the bottle upright in her right hand at chest height, label to camera"); do NOT phrase it as a swap ("replace the mug with the bottle"), which is REFUSED for free, because the product then comes out the size of whatever it replaced — a 30ml bottle rendered mug-sized in testing.'}, 'fixLabel': {'type': 'boolean', 'description': "default true: when the saved brand's product photo rides in this render, the product's label on the finished image is READ and compared with the photo, and re-printed from the photo at close range ONLY if it came out wrong (a label that is already right costs only the check, a credit or two; a re-print adds about ten). The reply says whether the label was checked, fixed or left as rendered (`labelPass`). Pass false when the user wants the packaging left exactly as generated: nothing is checked or re-printed."}, 'useBrand': {'type': 'boolean', 'description': 'default true: with no refImages, the server hydrates the SAVED brand’s product/logo references so the output lands on-brand; pass false for a pure prompt-only render'}, 'imageSize': {'type': 'string', 'description': 'pixel-size preset for models that support it: 1K/2K, and 4K on the models hermoso_capabilities lists with a 4K imageSize price (a 4K ask on any other model is refused, free) — omit for the default'}, 'refImages': {'type': 'array', 'items': {'type': 'string'}, 'description': 'local file paths or URLs of product/logo references to composite in. ON THE POSE MODELS — any row hermoso_capabilities marks `needsRefs` with a `refsMax`, such as putting your product in someone’s hands or a virtual try-on — THE ORDER IS THE CONTRACT AND IT IS NOT A COMPOSITE: refImages[0] is the PERSON photo, and the rest (up to `refsMax` minus one) are the product or garment photos. Reversed, you get the product wearing the person. A 4th product is dropped and the reply says so. Using a real person’s face, likeness or voice confirms you are them or have their consent, take full, unlimited responsibility for its use and will cover any claim against Hermoso (hermoso.ai/terms). Paid plan only; recorded.'}, 'aspectRatio': {'type': 'string', 'description': "e.g. '1:1', '9:16', '16:9', '4:5'. Each model draws its own list (hermoso_capabilities prints it per model, e.g. Nano Banana 2 goes to 1:8 and 8:1); a ratio the chosen model cannot draw is refused before anything is charged"}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['prompt'], 'properties': {'prompt': {'type': 'string', 'description': "the music in words, e.g. 'lo-fi jazz, brushed drums, 80 bpm'"}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['prompt'], 'properties': {'raw': {'type': 'boolean', 'description': 'RAW MODEL ACCESS: send the prompt with NO Hermoso system prompt — the model answers as itself rather than as an ad copywriter. Use it whenever the ask is not marketing copy (analysis, code, extraction, a plain question). Default false: the copywriter framing is applied.'}, 'model': {'type': 'string', 'description': 'a writing-model id from hermoso_capabilities (a Claude / Gemini / GPT / Llama / DeepSeek id) — omit for the default'}, 'prompt': {'type': 'string', 'description': 'the writing task / question'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['prompt'], 'properties': {'raw': {'type': 'boolean', 'description': "RAW MODEL ACCESS: dispatch this prompt to the model BYTE-IDENTICAL — no appended packaging/label guidance, no negative prompt, no reference-binding lines, no hex-to-colour-name rewrite. Use it when you want the model itself 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. Billing, durable delivery and per-model validation are unchanged."}, 'loop': {'type': 'boolean', 'description': 'true = a seamless loop whose last frame flows back into its first. Only models with loop true in hermoso_capabilities; needs refImage and cannot be combined with endImage.'}, 'audio': {'type': 'boolean', 'description': 'default true. false = render SILENT: no native model audio, no music bed, and no bed charge held or billed. This is the ONLY way to decline the automatic bed (see musicMood) — leave it alone for anything that should have sound, and do not combine it with ttsScript.'}, 'model': {'type': 'string', 'description': 'video model id from hermoso_capabilities; a named model is never swapped without asking. Omit to let the router pick'}, 'shots': {'type': 'array', 'items': {'type': 'object', 'required': ['prompt', 'seconds'], 'properties': {'prompt': {'type': 'string', 'description': 'what happens in this shot'}, 'seconds': {'type': 'integer', 'maximum': 15, 'minimum': 1, 'description': 'this shot’s length in whole seconds'}}}, 'description': 'MULTI-SHOT: one clip cut into these shots, in order. The seconds must add up to a length the model renders; replaces `prompt` (send either). Only models with multiShot true in hermoso_capabilities.'}, 'speak': {'type': 'string', 'description': 'A PERSON SAYING THESE EXACT WORDS TO CAMERA — the default for any "make my photo talk" / spokesperson / talk-to-camera ask. Pass with `creator` or a portrait as `refImage`: the video model films them saying it in their own voice, matched to who they are, and the length follows the words (leave durationSeconds out). `prompt` is then the staging (e.g. "natural", "walking in a park"). Real footage, not an animated photo; generate_avatar is the animated-photo look, only when asked for by name'}, 'angles': {'type': 'boolean', 'description': 'OPTIONAL, OFF BY DEFAULT: with `creator`, also send the extra views that creator already has saved (their pose plates, up to 2) beside their portrait. A test showed no visible improvement over the portrait alone, and each extra view adds about 25 s before the render starts, so leave it off unless asked. Views are skipped when the face library is busy, and the render always goes ahead on the portrait.'}, 'extend': {'type': 'boolean', 'description': 'true = EXTEND refVideo: the same clip continues per your prompt for durationSeconds more (each model’s extend.minSeconds..maxSeconds in hermoso_capabilities), delivered as ONE clip, source then continuation. Needs model named (a model with extend in hermoso_capabilities).'}, 'prompt': {'type': 'string', 'description': 'the video prompt / shot description (for a refVideo edit, this is the transformation instruction)'}, 'creator': {'type': 'string', 'description': 'STAR A SAVED CREATOR in this clip — their id from list_creators, or the name you know them by, or a PRESET AI creator from list_creators presets (exact name or id; free, no generation). Their saved portrait rides first among the references as the on-camera person, with their saved consent, exactly as render_ad casts them; a real person saved from a photo keeps their real face on camera. An unknown name is refused by name, nothing charged.'}, 'endImage': {'type': 'string', 'description': 'local path or URL of the LAST frame: the clip travels from refImage (required with it) to this image. Only models with endFrame true in hermoso_capabilities take it; any other named model is refused by name, nothing charged.'}, 'refImage': {'type': 'string', 'description': 'local path or URL to anchor the first frame. Using a real person’s face, likeness or voice confirms you are them or have their consent, take full, unlimited responsibility for its use and will cover any claim against Hermoso (hermoso.ai/terms). Paid plan only; recorded.'}, 'refVideo': {'type': 'string', 'description': 'URL of an existing video to EDIT rather than generate from scratch — the clip is transformed per your prompt, inheriting the SOURCE clip’s canvas (aspect ratio) and, on every model hermoso_capabilities marks `sourceLength`, its LENGTH too: those endpoints have no duration parameter, their listed `durations` are the per-second price ladder, and a durationSeconds you send is reported back as unused rather than silently dropped. Trim the source to change the length. Omit `model` for the default editor, or name a model whose videoEdit is true in hermoso_capabilities (a named model that cannot edit is refused, nothing charged); refImage rides along as the look of what the edit adds. With extend:true this is instead the clip to EXTEND. Omit to generate a fresh clip.'}, 'ttsVoice': {'type': 'string', 'description': 'voice name, e.g. Rachel / George'}, 'faceRoute': {'enum': ['face_lane'], 'type': 'string', 'description': 'ONLY after a render came back saying the video model\'s safety check flagged a person\'s face: \'face_lane\' is the user\'s choice "I own the rights to this face". Send the SAME request again with it and the same face is rendered on Seedance through the face library, at the normal price. Sending it is the user\'s confirmation that they have the rights to that face (paid plans, like every real face). Never set it on your own; the other choices that refusal names are a different model (`model`) or another creator (`creator`).'}, 'musicMood': {'type': 'string', 'description': 'WHICH mood the music bed is composed in (upbeat / calm / warm / epic / tense / playful / elegant / hype / chill / dramatic). It does NOT decide WHETHER there is one: a clip that comes back with no audio track — every model hermoso_capabilities lists as "silent", plus any audio model that returned mute — gets a bed composed and CHARGED automatically, at the flat per-track fee hermoso_capabilities reports as explainerMusicCredits, and omitting this field only means the mood defaults to "warm". Pass audio:false for a genuinely silent clip with no bed and no bed charge.'}, 'refImages': {'type': 'array', 'items': {'type': 'string'}, 'description': 'SEVERAL reference images (local paths or URLs) — a person, products, a place — that must all appear in the clip. Only models whose `refs.max` in hermoso_capabilities is above 1 use more than one, and each uses at most that many; with `refs.promptAddressed` true, name them in your prompt as Image 1, Image 2… in this order. minimax-h3-max-ref takes up to 9 and keeps each one as a reference rather than a first frame. On a model that takes one image, only the first is used.'}, 'ttsScript': {'type': 'string', 'description': 'voiceover script to speak'}, 'cameraMove': {'enum': ['orbit', 'orbit_left', 'orbit_half', 'orbit_full', 'rise', 'crane_up', 'push_in', 'pull_back', 'reveal'], 'type': 'string', 'description': 'A named camera move around the still in refImage, spelled exactly as the enum gives it: an orbit (a quarter turn, the default), orbiting left, a half turn or a full turntable, a rise, a crane up, a push in, a pull back, or a reveal. Only the camera-controls model renders one (minimax-h3-max-camera, listed with its moves in hermoso_capabilities): pass it with model omitted and that model is picked, or with that model named; any other named model is refused by name with nothing charged. Needs refImage.'}, 'resolution': {'enum': ['480p', '720p', '1080p', '4k'], 'type': 'string', 'description': "'1080p' default (what we ship and bill for); '480p'/'720p' = cheaper draft passes, '4k' = premium final delivery (more credits). NOT EVERY MODEL OFFERS EVERY TIER — this enum is what the tool accepts, and each model's OWN `resolutions` list in hermoso_capabilities is what it can actually render. Ask for a tier the chosen model does not list and it is rendered at that model's best available tier instead, with nothing in the reply saying so — so check `resolutions` before promising anyone 1080p or 4k."}, 'aspectRatio': {'type': 'string', 'description': "default '9:16'"}, 'interactionId': {'type': 'string', 'description': 'with extend:true on an Omni model: the interactionId returned by an earlier render on that model — continues it from its own stored context instead of re-uploading refVideo.'}, 'durationSeconds': {'type': 'number', 'description': 'length of THIS ONE clip in seconds — pick one of the CHOSEN model’s listed durations from hermoso_capabilities (never a generic guess: the lists differ per model, from 4–8s on the short models up to 30s on the longest-clip one). This is a single continuous generation, so it CANNOT exceed that model’s longest clip: a longer ask is REFUSED with nothing rendered and nothing charged (it is never quietly truncated). Past ~15s only the long-clip models qualify, and an unnamed render is routed by the narrower auto-pool — so NAME the model in `model` when you are asking for a long single take. For a spot longer than any one clip, use plan_ad with durationSeconds then render_ad, which stitches acts of at most one model clip each (on a 15s-clip model, 40s = 15+15+10).'}, 'cameraTrajectory': {'type': 'array', 'items': {'type': 'object', 'required': ['time', 'azimuth', 'elevation', 'distance'], 'properties': {'time': {'type': 'number', 'maximum': 1, 'minimum': 0, 'description': 'when this pose is reached, 0 = start of the clip, 1 = end'}, 'azimuth': {'type': 'number', 'description': 'horizontal angle around the subject in degrees (0 = where the still was taken; the sign turns the camera the other way; at most 32 full turns of total travel)'}, 'distance': {'type': 'number', 'description': 'distance from the subject in scene units, 1 = the distance of the still; smaller is closer', 'exclusiveMinimum': 0}, 'elevation': {'type': 'number', 'maximum': 90, 'minimum': -90, 'description': 'vertical angle in degrees, -90 (below) to 90 (straight above)'}}}, 'maxItems': 12, 'minItems': 2, 'description': '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.'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['text'], 'properties': {'text': {'type': 'string', 'description': 'the script to speak (≤900 characters)'}, 'voice': {'type': 'string', 'description': "a voice preset from the chosen engine (e.g. 'Aria'/'George' on eleven-v3, 'stokie_en' on seed-audio) — omit for the engine default"}, 'engine': {'type': 'string', 'description': "voice-engine id: 'seed-audio' (default), 'eleven-v3', 'minimax-speech', or 'kokoro' — listed in hermoso_capabilities"}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['fileId'], 'properties': {'fileId': {'type': 'string', 'description': 'the Drive file id (from list_drive_files)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['id'], 'properties': {'id': {'type': 'string', 'description': 'the job id, e.g. job_xxx'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['leadId'], 'properties': {'leadId': {'type': 'string'}, 'pageId': {'type': 'string', 'description': 'the company Page that owns the form — from list_linkedin_pages; omit when one Page is shared'}, 'adAccountId': {'type': 'string', 'description': 'read forms owned by an AD ACCOUNT instead of a Page'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['fileId'], 'properties': {'fileId': {'type': 'string', 'description': 'the OneDrive item id (from list_onedrive_files)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['name'], 'properties': {'name': {'type': 'string', 'description': 'bundle name from list_skills, e.g. hermoso-generate'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['image'], 'properties': {'brief': {'type': 'string', 'description': 'what to test, e.g. "price-led vs outcome-led" or "speak to busy parents"'}, 'count': {'type': 'integer', 'maximum': 10, 'minimum': 1, 'description': 'how many headlines to write when `headlines` is omitted (default 5)'}, 'image': {'type': 'string', 'description': 'the finished static ad: URL, Library item URL, upload_file URL or local path'}, 'fixLabel': {'type': 'boolean', 'description': 'false = leave the product label exactly as rendered: no product lookup, no label check, no re-print (default on when the ad shows the brand’s saved product)'}, 'headlines': {'type': 'array', 'items': {'type': 'string'}, 'description': 'your own headlines to test (up to 10); omit to have them written'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['video'], 'properties': {'plan': {'description': 'the `plan` object a previous dryRun returned, to render exactly those hooks without planning again'}, 'count': {'type': 'number', 'description': 'how many hook versions, 1-5 (default 3)'}, 'hooks': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string'}, 'cutAt': {'type': 'number', 'description': 'url: override the found payoff cut'}, 'start': {'type': 'number', 'description': 'url: skip the video’s own first seconds'}, 'prompt': {'type': 'string'}, 'mechanic': {'type': 'string'}}}, 'description': 'your own openings, one version each'}, 'notes': {'type': 'string', 'description': 'anything the hooks must respect, e.g. "keep it calm", "show the product in every hook"'}, 'video': {'type': 'string', 'description': 'the finished video to give new hooks: its served file URL'}, 'dryRun': {'type': 'boolean', 'description': 'true = return the plan and the quote, render nothing'}, 'useBrand': {'type': 'boolean', 'description': 'false = send no brand name or product photo (for a video that is not this workspace brand’s)'}, 'resolution': {'enum': ['480p', '720p', '1080p'], 'type': 'string', 'description': 'render tier for the new opening. Defaults to the source’s OWN tier so the hook matches the rest of the ad; a lower tier costs a lot less and is scaled into the source’s canvas (visibly softer for the first seconds). dryRun quotes whichever you pick.'}, 'hookSeconds': {'type': 'number', 'description': 'where the CURRENT hook ends, in seconds (1.5-4). Omit to use the first shot cut.'}, 'productImage': {'type': 'string', 'description': 'product photo URL used as a reference in hooks that show the product (defaults to the workspace brand’s first product photo)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['provider'], 'properties': {'limit': {'type': 'number', 'description': 'how many files to bring across in this call (default 10, max 25). Anything over the limit is listed as skipped so you know what is left.'}, 'folderId': {'type': 'string', 'description': 'the folder to import, from list_drive_files / list_onedrive_files (onlyFolders:true). Omit for the root of the drive.'}, 'provider': {'enum': ['drive', 'onedrive'], 'type': 'string', 'description': 'which cloud — `drive` is Google Drive, `onedrive` is Microsoft OneDrive'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['email'], 'properties': {'role': {'enum': ['member', 'admin'], 'type': 'string', 'description': 'default member'}, 'email': {'type': 'string', 'description': 'the invitee’s email'}, 'confirm': {'type': 'boolean', 'description': 'REQUIRED true — this invites a real person'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['provider'], 'properties': {'provider': {'type': 'string', 'description': 'provider id exactly as list_connectors reports it, e.g. "linkedin", "tiktok_ads", "meta"'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['provider'], 'properties': {'provider': {'enum': ['tiktok', 'x', 'youtube', 'threads', 'bluesky', 'telegram', 'reddit', 'pinterest', 'instagram', 'meta', 'google_ads', 'linkedin', 'pinterest_ads', 'linkedin_ads', 'reddit_ads', 'apple_ads', 'microsoft_ads', 'google_business', 'google_analytics', 'snapchat_ads', 'x_ads', 'tiktok_ads', 'google_tag_manager', 'google_search_console', 'bing_webmaster'], 'type': 'string', 'description': 'which connector’s accounts to list'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'limit': {'type': 'number', 'description': 'max creators to return (default 24)'}, 'gender': {'enum': ['female', 'male'], 'type': 'string', 'description': 'filter the PRESET creators by gender (the saved cast is never filtered)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'query': {'type': 'string', 'description': 'only files whose name contains this'}, 'folderId': {'type': 'string', 'description': 'list the contents of this folder id'}, 'pageSize': {'type': 'number', 'description': 'rows per page (1–200, default 50)'}, 'pageToken': {'type': 'string', 'description': 'cursor from a previous call'}, 'onlyFolders': {'type': 'boolean', 'description': 'list folders only'}, 'includeTrashed': {'type': 'boolean', 'description': 'include trashed files (default false)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'kind': {'enum': ['ours', 'user', 'unknown'], 'type': 'string', 'description': "'ours' = a defect; 'user' = a refusal we authored; 'unknown' = we could not tell"}, 'limit': {'type': 'number', 'description': 'how many groups to return (default 50, max 200)'}, 'since': {'type': 'string', 'description': 'ISO timestamp — only groups last seen at or after this'}, 'surface': {'enum': ['http', 'mcp', 'agent', 'job', 'client'], 'type': 'string', 'description': 'where it happened: http (an API route), mcp (an agent tool), agent (the in-app Studio agent), job (an async render/publish), client (a browser crash)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'tier': {'enum': ['luxury', 'premium', 'drugstore'], 'type': 'string', 'description': 'product tier, used with category — changes the FINISH of the room, never the room. Default premium.'}, 'channel': {'type': 'string', 'description': 'restrict the performance half to one channel (facebook, instagram, threads, x, linkedin, youtube, tiktok, reddit, pinterest)'}, 'category': {'type': 'string', 'description': "the product category (e.g. 'skincare serum', 'protein powder', 'sunglasses') — returns the setting our Location x Tier matrix puts that category in, with the reason"}, 'authentic': {'type': 'boolean', 'description': 'true if the planned ad is an authentic/UGC/creator-register render — on-screen-text hooks are then reported unusable, with the reason'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'kind': {'enum': ['image', 'video', 'all'], 'type': 'string', 'description': "filter by asset kind (default 'all')"}, 'limit': {'type': 'number', 'description': 'max assets to return (default 20, max 60)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'limit': {'type': 'number'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'pageId': {'type': 'string', 'description': 'the company Page that owns the form — from list_linkedin_pages; omit when one Page is shared'}, 'adAccountId': {'type': 'string', 'description': 'read forms owned by an AD ACCOUNT instead of a Page'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'limit': {'type': 'number', 'description': 'per page, max 100'}, 'since': {'type': 'string', 'description': 'ISO date or epoch milliseconds'}, 'start': {'type': 'number', 'description': 'offset for the next page'}, 'until': {'type': 'string'}, 'formId': {'type': 'string', 'description': 'only this form (from list_linkedin_lead_forms)'}, 'pageId': {'type': 'string', 'description': 'the company Page that owns the form — from list_linkedin_pages; omit when one Page is shared'}, 'leadType': {'enum': ['SPONSORED', 'COMPANY', 'EVENT', 'ORGANIZATION_PRODUCT'], 'type': 'string', 'description': 'defaults by owner: SPONSORED for an ad account, COMPANY (organic Page form) for a Page; EVENT for event forms. LinkedIn refuses SPONSORED on a Page owner'}, 'adAccountId': {'type': 'string', 'description': 'read forms owned by an AD ACCOUNT instead of a Page'}, 'formVersion': {'type': 'number', 'description': 'default 1'}, 'testLeadsOnly': {'type': 'boolean', 'description': 'true returns ONLY test submissions'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'pageId': {'type': 'string', 'description': 'the company Page that owns the form — from list_linkedin_pages; omit when one Page is shared'}, 'leadType': {'type': 'string'}, 'adAccountId': {'type': 'string', 'description': 'read forms owned by an AD ACCOUNT instead of a Page'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'limit': {'type': 'number', 'description': 'max items (default 50, max 200)'}, 'category': {'type': 'string', 'description': 'filter to one bucket (Brand/Audience/Taste/Do/Don’t/Preference)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'brand': {'type': 'string', 'description': 'WHICH BRAND the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a brand this connection is not pinned to; applies to THIS CALL ONLY. A name that matches no brand, or two, is REFUSED and nothing is done.'}, 'limit': {'type': 'number', 'description': 'how many posts (default 25, max 100)'}, 'cursor': {'type': 'string', 'description': 'paging cursor returned by a previous call'}, 'pageId': {'type': 'string', 'description': 'which connected Page — omit when the brand has only one'}, 'target': {'enum': ['facebook', 'instagram'], 'type': 'string', 'description': "default facebook; 'instagram' reads the Page's linked IG business account"}, 'account': {'type': 'string', 'description': 'which Instagram account — an @handle or id from list_connector_accounts("instagram"). Needed when several are linked, and the way an Instagram Login (standalone) account is reached; omit for the one Page-linked account.'}, 'includeUnpublished': {'type': 'boolean', 'description': 'Facebook only — also return unpublished drafts (hidden by default)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'query': {'type': 'string', 'description': 'search — only items whose name matches this'}, 'folderId': {'type': 'string', 'description': 'list the contents of this folder id'}, 'pageSize': {'type': 'number', 'description': 'rows per page (1–200, default 50)'}, 'pageToken': {'type': 'string', 'description': 'cursor from a previous call'}, 'onlyFolders': {'type': 'boolean', 'description': 'list folders only'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'privacy': {'enum': ['ALL', 'PUBLIC', 'PROTECTED', 'SECRET'], 'type': 'string', 'description': 'filter by board privacy; default is everything the connection can see'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'full': {'type': 'boolean', 'description': 'true to return every hook/angle/play in the text, not just the headline counts'}, 'limit': {'type': 'number', 'description': 'max playbooks to return (default 25, max 100)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'brandId': {'type': 'string', 'description': 'a brand id/name from list_brands whose product library to list; omit to use the active brand'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'brand': {'type': 'string', 'description': 'WHICH BRAND the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a brand this connection is not pinned to; applies to THIS CALL ONLY. A name that matches no brand, or two, is REFUSED and nothing is done.'}, 'limit': {'type': 'number', 'description': 'max posts (default 50, max 200), newest first'}, 'cursor': {'type': 'string', 'description': 'nextCursor from a previous reply: the next, older page'}, 'channel': {'type': 'string', 'description': 'filter to one channel: facebook, instagram, threads, x, linkedin, youtube, tiktok, reddit, pinterest, google_business'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'id': {'type': 'string', 'description': 'one post id from this list: returns that post in full, every caption and setting included'}, 'brand': {'type': 'string', 'description': 'WHICH BRAND to list — the id or exact name from list_brands. Needed when the post lives in a brand this connection is not pinned to: a post you can CREATE in a brand must be manageable there too, without switching the whole connection. A name that matches no brand, or two, is REFUSED.'}, 'fired': {'type': 'number', 'description': 'how many already-fired posts to list, most recent last (default 15, max 200)'}, 'channel': {'type': 'string', 'description': 'only posts that include this channel, e.g. "pinterest" or "x"'}, 'upcoming': {'type': 'number', 'description': 'how many queued posts to list, soonest first (default 25, max 200)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'sheetUrl': {'type': 'string', 'description': 'a Google Sheets URL — the id is extracted from it'}, 'spreadsheetId': {'type': 'string', 'description': 'the spreadsheet id (from create_sheet, or list_drive_files for one the user picked)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'limit': {'type': 'number', 'description': 'max ads to return (default 50, max 500)'}, 'collection': {'type': 'string', 'description': 'only list ads in this collection (by name or id) — omit for every collection'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'limit': {'type': 'number', 'description': 'how many recent updates to scan, 1–100 (default 100)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'limit': {'type': 'number', 'description': 'max findings to return (default 25, max 75 — the server keeps at most 75, and at most 15 per brand)'}, 'competitor': {'type': 'string', 'description': 'only findings for this watched brand (exact name as returned in `watching`) — omit for all of them'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['image', 'languages'], 'properties': {'image': {'type': 'string', 'description': 'the finished static ad: URL, Library item URL, upload_file URL or local path'}, 'fixLabel': {'type': 'boolean', 'description': 'false = leave the product label exactly as rendered: no product lookup, no label check, no re-print (default on when the ad shows the brand’s saved product)'}, 'languages': {'type': 'array', 'items': {'type': 'string'}, 'maxItems': 5, 'minItems': 1, 'description': 'target languages, by name'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'lane': {'enum': ['blocks', 'stills'], 'type': 'string', 'description': "'blocks' (default) = 10-second video blocks, real motion, one narrated line per block; 'stills' = the picture film (a still about every 1.5s, narrated, no video model; cheaper). Quote either with dryRun."}, 'music': {'type': 'string', 'description': "music bed under the narration, measured to sit about 14 dB under the voice and sidechain-ducked beneath it. Omit and the KIDS and FAIRYTALE channels get their recommended bed COMPOSED for this film — those two are the only channels a bed is due on unasked, and it costs a small flat fee; every other channel ships dry. 'off' forces silence. 'library' takes a free curated track only, and ships dry when none is on file. NAME A MOOD (upbeat / calm / warm / epic / tense / playful / elegant / hype / chill / dramatic) or DESCRIBE it in words to compose one on ANY channel, at the same fee. hermoso_capabilities reports the exact figure as explainerMusicCredits; quote it before you turn a bed on or pick a mood."}, 'style': {'type': 'string', 'description': "visual style: 'cinematic' (default, photoreal); styled shortcuts editorial_collage, flat_vector, stickman, whiteboard, ink_marker, silhouette, storybook, paper_diorama, isometric, claymation, pixel_art, watercolor, fluffy_toy, low_poly, stylized_3d, studio_3d (the Kids default), mannequin; or ANY look described in words ('80s anime cel animation'), locked across every frame. Ask rather than pick silently; a styled look costs more."}, 'topic': {'type': 'string', 'description': 'what the explainer should teach or explain — a topic or a short brief (host_episode: or pass `script`)'}, 'voice': {'type': 'string', 'description': 'narration voice name — omit for the default warm read'}, 'dryRun': {'type': 'boolean', 'description': 'true = return the exact credits this explainer reserves (its own pricing, stopped at the hold) and render nothing. Quote it before running one; try frameDensity lean or minimal when the balance is short.'}, 'format': {'enum': ['explainer', 'faceless_channel', 'host_episode'], 'type': 'string', 'description': "blocks lane: 'explainer' (default; Gemini Omni 720p, 16:9 or 9:16, runs exactly the length asked) or 'faceless_channel' (a YouTube/TikTok faceless channel video; MiniMax H3 at 2K, 16:9 by default, five hard cuts per 10s block, a whole number of blocks). Omit and a history / kids / fairytale channel is a faceless channel video. 'host_episode' = the AI host episode (see the top)."}, 'script': {'type': 'string', 'description': 'host_episode: the exact words, said verbatim and split at natural breaks into 4-30s pieces'}, 'cameras': {'type': 'array', 'items': {'type': 'string'}, 'description': 'host_episode: the rotation, ids frontal / three_quarter / close or framings in words; one entry = one fixed camera'}, 'channel': {'enum': ['explainer', 'history', 'kids', 'fairytale'], 'type': 'string', 'description': "the CHANNEL TYPE — it sets the pacing, the narration register and the default look, and is orthogonal to `style` (a named style always wins): explainer (casual second-person, fast cuts), history (witty chronological retelling / documentary), kids (fastest, question-first, warm teacher), fairytale (slow, atmospheric myth or folklore). Default 'explainer'."}, 'creator': {'type': 'string', 'description': 'host_episode: the host, a saved creator or a preset by name or id (list_creators)'}, 'endCard': {'type': 'boolean', 'description': 'append the branded end card. DEFAULT FALSE — set true ONLY when the user asks for one'}, 'setting': {'type': 'string', 'description': 'host_episode: the set in words (default: written to fit the topic)'}, 'upscale': {'type': 'number', 'description': 'optional FINAL upscale — 2 doubles each side, 4 quadruples. Captions and the end card are burned BEFORE it so they upscale with the frame. It is priced BY LENGTH and it is the expensive part — several times the cost of rendering the film itself. hermoso_capabilities reports the exact figures per length as explainerUpscaleCredits. Never turn it on unasked: quote the number and let the user choose.'}, 'captions': {'type': 'boolean', 'description': 'turn ON-SCREEN TEXT on. DEFAULT FALSE, and leave it false unless the user asks — the narration already says the point and the pictures carry it, so the clean film is the better default. `captions:true` on its own burns SUBTITLES (see below), because that is what a caption is for: showing what is being said when the phone is on mute. Slim white CAPS, thin black outline, bottom safe band, no plate, no box.'}, 'brandName': {'type': 'string', 'description': 'brand name for the end card — omit to leave it unbranded'}, 'fromDraft': {'type': 'string', 'description': "host_episode: a finished draft's job id, to render it in HD (720p) with the same script, cameras, set and host"}, 'hostImage': {'type': 'string', 'description': "host_episode: a photo URL of the host instead (upload_file for a local file); a real person's face needs a paid plan"}, 'subtitles': {'type': 'boolean', 'description': 'which on-screen text, once `captions` is on. LEAVE IT UNSET (or true) for SUBTITLES — every spoken word, in order, timed to the narration; free, no extra render, no extra credits, and there is NO cue limit, so the whole film is subtitled however long it runs (at most 5 words / 32 characters a line). Set it FALSE only if the user explicitly wants section HEADINGS instead: one short summary label held over each ~7-15s section. That is NOT what is being said — it is a label about it — so it is the wrong answer to "add captions" and to anyone watching on mute. `subtitles:true` also implies `captions:true`. TIMING: each cue is anchored to that section’s REAL measured narration length and distributed inside the section by character count — exact at every section boundary, approximate to a few tenths of a second within one. It is not a word-level speech clock, so never promise frame-accurate sync.'}, 'thumbnail': {'type': 'boolean', 'description': 'host_episode: one thumbnail of the host (default true)'}, 'aspectRatio': {'enum': ['9:16', '16:9', '1:1', '4:5', '3:4'], 'type': 'string', 'description': "'9:16' default (a faceless channel video and a host episode default to 16:9)"}, 'frameDensity': {'enum': ['standard', 'lean', 'minimal'], 'type': 'string', 'description': "how many pictures per second of narration, and therefore what it costs. 'standard' (default) is a frame about every 1.5s — the density a stills film needs to read as a film rather than a slideshow; 'lean' is one about every 2.5s (the longest hold that still reads as a film, ~40% of the frames and ~40% of the cost); 'minimal' is one picture about every 3.5s, the cheapest and the longest any still is ever held, and it reads close to a slideshow. Only drop below the default if the user asked for something cheaper."}, 'durationSeconds': {'type': 'number', 'description': 'target length 20-120s (default 60); drives the section count — ~10s of narration each, 3-8 sections'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['sound'], 'properties': {'image': {'type': 'string'}, 'sound': {'type': 'string'}, 'video': {'type': 'string'}, 'seconds': {'type': 'number'}, 'soundEnd': {'type': 'number'}, 'soundStart': {'type': 'number'}, 'videoStart': {'type': 'number'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['config'], 'properties': {'config': {'type': 'object', 'properties': {}, 'description': "MUST include config.template: 'custom' or a preset id, plus its fields. PRESETS: 'slideshow' (IMAGES, TikTok photo mode / Reels 1080x1920, or size:'4:5' feed carousels; no branding): { slides:[{text, sub?, image?, blur?, background?, position?}] (2-35; words never rewritten), style? ('tiktok-classic'|'clean-minimal'|'note-style' or a look in words), textStyle?, video?:true (+ an MP4) }; 2 credits, +1 per slide past 5, +2 for the MP4. 'imessage-chat' (VIDEO ~15s): { thread:{contactName, messages:[{from:'them'|'me', text?, product?:{image,title,domain}}]}, theme?, endCard }. 'chatgpt-chat' (VIDEO): { question, answer (may **bold** the brand), productImage?, endCard }. 'apple-notes' (VIDEO): { title, lines[], theme?, endCard }. 'value-prop' (VIDEO ~17s): { hook ≤40ch, claims[3-5 ≤34ch], productImages[2-3], palette[], endCard }. 'static-mockup' (IMAGE): { style:'imessage'|'notes'|'card', size?:{w,h}, ...fields }. 'airdrop-carousel' (VIDEO): { brandName, products:[{image, title?}] (3-16), endCard }. 'app-ui-tour' (VIDEO): { hook?, appName, iconImage?, beats:[{screenImage, caption}] (2-6), endCard }. 'imessage-cascade' (VIDEO): { notifications:[{sender, text}] (4-8), backgroundImage?, endCard }. 'photo-grid' (VIDEO): { title?, photos:[{image, label?}] (4-9), endCard }. 'vignette' (VIDEO): { hook, lines[2-4 ≤40ch], heroImage, endCard }. 'kinetic-type' (VIDEO, own SFX): { phrases[3-6 ≤34ch], productImages?[≤4], endCard }. 'myth-vs-fact' (VIDEO with a real VOICEOVER, small extra charge): { pairs:[{myth ≤50ch, fact ≤60ch}] (2-4; [brackets] accent), endCard }, real truths only. 'carousel' (IMAGES, 5-10 branded 1080x1080): { cover:{hook?, title}, slides:[{headline, support?, stat?:{value, label}}] (3-8), cta:{headline, cta?, domain?}, productImage?, logo? }. endCard = { headline, cta, domain?, logo?, color? }; palette and fontStack optional.", 'additionalProperties': {}}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'font': {'type': 'string', 'description': 'headline font: Anton (default) or any Google Fonts family'}, 'logo': {'type': 'string', 'description': 'a brand logo URL or path to place into the composition'}, 'split': {'type': 'object', 'required': ['mode'], 'properties': {'mode': {'enum': ['plain', 'before_after', 'versus', 'custom'], 'type': 'string'}, 'panels': {'type': 'array', 'items': {'type': 'string'}}}, 'description': 'split/panel LAYOUT — only when the user asks for one ("split", "before/after", "versus screen"). "X vs Y" as a SCENE stays one unified frame', 'additionalProperties': {}}, 'takes': {'type': 'number', 'description': 'camera takes per emotion, 1–4: designed framing / low-angle hero / extreme close-up / wide dutch tilt'}, 'topic': {'type': 'string', 'description': "the video's topic — used to pick the hero object when you don't name keyElements"}, 'tweak': {'type': 'object', 'required': ['kind', 'value'], 'properties': {'kind': {'type': 'string'}, 'value': {'type': 'string'}}, 'description': 'surgical pixel-faithful edit of a FINISHED thumbnail (needs sourceImage): kind emotion / background / background_color / rim_light, or any other kind with the edit in words as value'}, 'logo3d': {'type': 'boolean', 'description': 'first turn the flat logo into a volumetric 3D render (one extra billed image), then composite that'}, 'people': {'type': 'array', 'items': {'type': 'object', 'required': ['describe'], 'properties': {'describe': {'type': 'string'}}, 'additionalProperties': {}}, 'description': 'people described in prose instead of by photo (each still gets the chosen expression)'}, 'emotion': {'type': 'string', 'description': "the expression on the face (default 'shock') — shock · hype · fear · confusion · determination · smug · charisma · disgust · awe · rage · laugh, or your own phrase"}, 'bakeText': {'type': 'boolean', 'description': 'default false. true paints the headline INTO the generation — only on an explicit user ask; it leaks garbled text elsewhere in the frame'}, 'emotions': {'type': 'array', 'items': {'type': 'string'}, 'description': 'render one variant per emotion (variants = emotions × takes, max 16)'}, 'headline': {'type': 'string', 'description': '2–4 word headline. Typeset OVER the finished render by default (free, always legible); newlines split it into stacked lines'}, 'location': {'type': 'string', 'description': 'place, time of day, weather, atmosphere'}, 'rimColor': {'type': 'string', 'description': "colored back+hair light — ONLY when the user names one: 'ice-blue' / 'neon-magenta' / 'toxic-lime' / 'amber-gold' / 'pure-white'"}, 'variants': {'type': 'number', 'description': 'how many thumbnails to render (default 1, max 16). Each is its own billed render — offer a set of 4 rather than assuming'}, 'framework': {'type': 'string', 'description': "concept framework id (default 'posed_portrait') — before_after · social_ui · three_step · screenshot · posed_portrait · posed_action · specific_day · graphical · landscape · map_aerial · product · adding_text · repetition · size_difference · news_clip · amplified_reality — or your own concept in words"}, 'reference': {'type': 'object', 'properties': {}, 'description': "fields YOU extracted by eye from a reference thumbnail. Extract ALL of: brief (one dense sentence on the concept), subject (pose/action generically, NEVER a specific identity), elements, location, composition, background, split (boolean), split_count, person_count (0-3), emotion (one of the 11 presets or 'other'), emotion_detail (one vivid sentence covering eyes, brows, mouth, head angle). emotion + emotion_detail carry the reference's actual facial performance, which is the single biggest CTR lever on a face; split/split_count reproduce its panel structure. The reference image itself is never sent to the model", 'additionalProperties': {}}, 'background': {'type': 'string', 'description': 'override the default bold saturated colour-field background'}, 'faceImages': {'type': 'array', 'items': {'type': 'string'}, 'description': 'up to 3 face photos (URLs or local paths) — each becomes a locked CHARACTER identity, in order'}, 'sceneBrief': {'type': 'string', 'description': 'what the thumbnail depicts — the concept in one dense sentence, rendered exactly'}, 'aspectRatio': {'type': 'string', 'description': "'16:9' (YouTube, default) / '9:16' (Shorts) / '4:5' (Instagram) / '4:3' / '1:1'"}, 'bakedUiText': {'type': 'string', 'description': 'short label for a text-carrying framework (a chat bubble, a DAY N badge, a news lower-third, a map callout) — needs frameworkRequested:true'}, 'composition': {'type': 'string', 'description': 'override the default large-foreground-subject composition'}, 'keyElements': {'type': 'string', 'description': 'signature props / effects that make it pop — oversized, flying toward camera'}, 'sourceImage': {'type': 'string', 'description': 'the finished thumbnail URL a `tweak` edits; tweaks chain, so feed each accepted output into the next'}, 'overlayStyle': {'type': 'string', 'description': 'headline style: beast (default), fire, neon-lime, clean-glass, marker, or your own CSS declarations'}, 'forceGenerate': {'type': 'boolean', 'description': "render the 'screenshot' framework anyway (it is normally a real video frame, not a generation)"}, 'headlineLines': {'type': 'array', 'items': {'type': 'string'}, 'description': 'explicit headline lines (up to 3) — overrides splitting `headline` on newlines'}, 'headlinePlace': {'type': 'string', 'description': 'bottom (default), top, center, or a 0-1 fraction from the top; never over the face'}, 'restrainedGrade': {'type': 'boolean', 'description': 'true for a calm / premium / muted look instead of the default punchy poster grade'}, 'castGenericPerson': {'type': 'boolean', 'description': 'pass true only after the user has explicitly chosen a generated stranger over their own face'}, 'frameworkRequested': {'type': 'boolean', 'description': 'true ONLY when the USER named this framework — it is what authorizes a text-carrying framework (social_ui / news_clip / specific_day / map_aerial) to bake its short UI label'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['action'], 'properties': {'tab': {'type': 'string', 'description': 'which tab — its title or numeric sheetId (rename / delete)'}, 'title': {'type': 'string', 'description': 'the name for the new tab (action:"add")'}, 'action': {'enum': ['add', 'rename', 'delete'], 'type': 'string'}, 'confirm': {'type': 'boolean'}, 'newTitle': {'type': 'string', 'description': 'what to rename the tab to (action:"rename")'}, 'sheetUrl': {'type': 'string'}, 'confirmCells': {'type': 'number'}, 'spreadsheetId': {'type': 'string'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'brandId': {'type': 'string', 'description': 'a brand id/name from list_brands to mine for; omit to use the active brand'}, 'reviews': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}}, {'type': 'string'}], 'description': 'your own customer reviews: a list of review texts, or one pasted block (one per line, numbered, blank-line separated, or CSV with a review column)'}, 'reviewsUrl': {'type': 'string', 'description': 'a URL of your reviews: an uploaded CSV, TXT or JSON file (from upload_file) or a review page. On a local CLI a file path also works.'}, 'useOwnReviewsOnly': {'type': 'boolean', 'description': 'true = mine only your reviews, no public search (no search credits). Default false = merge with public customer language.'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['video'], 'properties': {'axes': {'type': 'array', 'items': {'type': 'string'}, 'description': 'what to vary: character, outfit, location, objects (default all four), or your own, e.g. "season"'}, 'count': {'type': 'number', 'description': 'how many variants, 1-12 (default 6)'}, 'notes': {'type': 'string', 'description': 'anything the variants must respect, e.g. "keep it women 25-40", "no gyms"'}, 'video': {'type': 'string', 'description': 'the source video URL'}, 'change': {'type': 'string', 'description': "what should change, in the user's own words, e.g. 'older women, winter streets'; every variant applies it (omit to let the variants vary freely)"}, 'dryRun': {'type': 'boolean', 'description': 'true = return the plan and the quote, render nothing'}, 'regions': {'type': 'array', 'items': {'type': 'string'}, 'description': 'markets to restyle for, one or more variants each, e.g. ["Berlin","Tokyo","São Paulo"] — visuals only; audio is never translated here'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['product'], 'properties': {'hook': {'type': 'string', 'description': 'force the VISUAL scroll-stop mechanic the opening beat is built on — a hook id from list_hooks (e.g. "direct_callout", "mid_problem", "macro_asmr"). Omit to let the planner pick. A hook that cannot be delivered in this brief is DROPPED with the reason rather than rendered wrongly — an on-screen-text hook on an authentic/UGC ad is the one that bites, because that register carries zero on-screen text.'}, 'brand': {'anyOf': [{'type': 'string'}, {'type': 'object', 'properties': {}, 'additionalProperties': {}}], 'description': 'brand name, or a brand profile object {name,domain,category,palette,products,…}. OMIT to use the workspace’s SAVED brand + memory automatically (see get_brand); use draft_brand to onboard a new one'}, 'draft': {'type': 'object', 'required': ['model', 'durationSeconds'], 'properties': {'model': {'type': 'string'}, 'durationSeconds': {'type': 'number'}}, 'description': 'ONLY after a video refusal that offered a light draft: the {model, durationSeconds} it named. The plan is then authored to that length and priced on that model. Never invent one — a video the account cannot cover is refused BEFORE planning with the three options (image / add credits / this draft when one fits), and the user chooses.'}, 'format': {'enum': ['auto', 'image', 'video'], 'type': 'string', 'description': "'image', 'video', or 'auto' when unspecified"}, 'recipe': {'type': 'string', 'description': 'a recipe id from hermoso_capabilities to force an archetype'}, 'talent': {'enum': ['auto', 'creator', 'product_only'], 'type': 'string', 'description': "who is on camera; omit to follow the format. Say 'someone new' in product to skip reusing a saved creator."}, 'product': {'type': 'string', 'description': 'what to advertise + any angle/offer the user specified'}, 'setting': {'type': 'string', 'description': 'force the WHERE — a setting id from list_hooks (e.g. "kitchen", "gym", or a surreal one like "volcano_rim" / "airplane_wing", which are played 100% straight and never acknowledged). Omit for a neutral setting.'}, 'language': {'type': 'string', 'description': 'output language for the ad copy (e.g. Spanish) — default English'}, 'reference': {'type': 'string', 'description': 'a reference to clone: an ad-library link (Facebook Ad Library, LinkedIn Ad Library, Google Ads Transparency — its real copy/advertiser are fetched) OR a VIDEO link — a TikTok, Instagram Reel, Facebook video, X post, YouTube Short/video or a direct video file — which is WATCHED first (frames + voiceover/on-screen-text transcript) so the concept keeps its hook, structure and pacing. To remake one video for this brand at its own length, clone_video is the direct tool'}, 'durationSeconds': {'type': 'number', 'description': 'VIDEO ONLY — the total spot length the user explicitly asked for, in seconds, copied verbatim (30 for "a 30 second ad"). The planner authors the storyboard TO it: the scenes’ seconds sum to it and the script is word-budgeted for it. Supported range 4–180; anything outside is CLAMPED to it (the reply says so). A length that fits ONE clip of the render model renders as a single continuous pass; anything longer is STITCHED from acts filled to that model’s clip maximum with the remainder last (on a 15s-clip model, 40 -> 15+15+10 and 17 -> 13+4) — never time-compressed. The maximum is the model’s own: 15s on most, 30s on the longest-clip model, so a 30s ad can be one unbroken take rather than two acts. Omit when the user named no length; do NOT pass a guess: an omitted value is the default, a 30s spot in one unbroken take (2026-09-29), and a length the user names always wins.'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['product'], 'properties': {'brand': {'anyOf': [{'type': 'string'}, {'type': 'object', 'properties': {}, 'additionalProperties': {}}], 'description': 'brand name or profile object; OMIT to use the workspace’s saved brand'}, 'count': {'type': 'integer', 'maximum': 8, 'minimum': 2, 'description': 'how many distinct variants (default 6)'}, 'product': {'type': 'string', 'description': 'what to advertise'}, 'language': {'type': 'string', 'description': 'output language for the variant copy (e.g. Spanish) — default English'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['videoUrl', 'ops'], 'properties': {'ops': {'type': 'array', 'items': {'type': 'object', 'required': ['op'], 'properties': {'x': {'type': 'number', 'description': 'text/watermark centre: 0-1 of frame, or px'}, 'y': {'type': 'number'}, 'db': {'type': 'number', 'description': 'audio_gain -20..+6 dB / music: trim on its automatic level, -20..+12'}, 'op': {'enum': ['trim', 'speed', 'mute', 'audio_gain', 'fade_out', 'music', 'append_card', 'watermark', 'grain', 'text', 'join'], 'type': 'string'}, 'end': {'type': 'number', 'description': 'trim/mute/text window end (s)'}, 'sub': {'type': 'string', 'description': 'append_card: pill line (default: website) / text: a second, smaller line'}, 'duck': {'anyOf': [{'type': 'string', 'const': 'auto'}, {'type': 'boolean'}], 'description': "music: 'auto' default = dips while the clip's own sound plays"}, 'from': {'type': 'number', 'description': 'music: the second of the track to start from'}, 'mood': {'type': 'string', 'description': 'music: mood words for the library pick, or the brief for generated music'}, 'text': {'type': 'string', 'description': 'text: the words, verbatim'}, 'clips': {'type': 'array', 'items': {'type': 'object', 'required': ['url'], 'properties': {'end': {'type': 'number'}, 'url': {'type': 'string'}, 'match': {}, 'start': {'type': 'number'}, 'reframe': {}}}, 'description': 'join: the clips after this video'}, 'match': {'description': "join: seam match, 'auto' default | 'off' | {grade,level,grain,blur,strength}"}, 'start': {'type': 'number', 'description': 'trim/mute/text window start (s)'}, 'style': {'anyOf': [{'type': 'string'}, {'type': 'object', 'properties': {}, 'additionalProperties': {}}], 'description': 'text: a look name or a textStyle'}, 'track': {'type': 'string', 'description': "music: omit = a library track by mood; a library track name; an audio link; 'generate' (paid, only on the user's ask)"}, 'bridge': {'type': 'object', 'required': ['kind'], 'properties': {'kind': {'enum': ['impact', 'text'], 'type': 'string'}, 'text': {'type': 'string'}, 'then': {'type': 'string'}, 'cutAt': {'type': 'number', 'description': 'omit: found from the footage'}, 'flash': {'type': 'boolean'}, 'shake': {'type': 'boolean'}, 'sound': {'type': 'string', 'description': "'auto' default, 'own', 'impact', 'whoosh', 'none', or an audio URL (find_sound)"}, 'matchCut': {'type': 'number'}}, 'description': 'join: connects the hook to the first clip'}, 'corner': {'enum': ['tl', 'tr', 'bl', 'br'], 'type': 'string', 'description': 'watermark corner (default br), or x/y'}, 'factor': {'type': 'number', 'description': 'speed 0.5-2'}, 'fadeOut': {'type': 'number', 'description': 'music: fade-out seconds 0-5'}, 'reframe': {'description': "join: shot change at a same-framing stitch, 'auto' | 'off' | step 1.1-1.5"}, 'replace': {'type': 'boolean', 'description': "music: true = replaces the clip's own sound"}, 'seconds': {'type': 'number', 'description': 'fade_out 0.3-3s / append_card 2-5s / transition 0.2-1.5s'}, 'tagline': {'type': 'string', 'description': 'append_card: smaller line under the headline'}, 'headline': {'type': 'string', 'description': 'append_card: big line (default: brand name)'}, 'position': {'enum': ['top', 'center', 'lower', 'bottom'], 'type': 'string', 'description': 'text: where, or x/y'}, 'card_html': {'type': 'string', 'description': 'append_card: your OWN full-frame card as inline-styled HTML ({{logo}} = the brand logo)'}, 'intensity': {'anyOf': [{'enum': ['default', 'strong'], 'type': 'string'}, {'type': 'number'}], 'description': 'grain: or 0-1 (default 0.25)'}, 'textStyle': {'anyOf': [{'type': 'string'}, {'type': 'object', 'properties': {}, 'additionalProperties': {}}]}, 'background': {'type': 'string', 'description': "append_card: hex or a colour name; the user's colour beats the brand palette"}, 'transition': {'type': 'string', 'description': "join: 'cut' default, 'crossfade', or any ffmpeg xfade name (wipeleft…)"}}}, 'description': 'the ordered edit plan (max 6 ops)'}, 'accent': {'type': 'string', 'description': 'override the brand accent hex'}, 'domain': {'type': 'string', 'description': 'override the brand website'}, 'dryRun': {'type': 'boolean', 'description': 'true = return the exact credits this edit reserves and run nothing'}, 'videoUrl': {'type': 'string', 'description': 'the video to edit: a render / Library URL, a direct file, or a public post link'}, 'brandName': {'type': 'string', 'description': 'override the workspace brand name'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'axis': {'enum': ['hook', 'subject', 'recipe', 'channel', 'media', 'hour'], 'type': 'string', 'description': 'what to group by — default hook; recipe = the format of the creative'}, 'days': {'type': 'number', 'description': 'look back N days (1-730) over the whole history; omit for the recent posts only'}, 'brand': {'type': 'string', 'description': 'WHICH BRAND the post lives in — id or exact name from list_brands (a shared workspace: its profile id). Needed when it was published in a brand this connection is not pinned to; applies to THIS CALL ONLY. A name that matches no brand, or two, is REFUSED and nothing is done.'}, 'channel': {'type': 'string', 'description': 'restrict to one channel'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['text'], 'properties': {'hook': {'type': 'string', 'description': "the post's angle — a list_hooks id or your own wording, reused exactly"}, 'text': {'type': 'string', 'description': 'The post, up to 300 characters / 3000 UTF-8 bytes.'}, 'brand': {'type': 'string', 'description': "WHICH BRAND this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no brand, or two, is REFUSED and nothing is posted."}, 'langs': {'type': 'array', 'items': {'type': 'string'}, 'description': "BCP-47 language tags, e.g. ['en']."}, 'ideaId': {'type': 'string', 'description': 'short id of the content-plan idea this post came from'}, 'recipe': {'type': 'string', 'description': 'the post\'s FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format'}, 'account': {'type': 'string', 'description': 'WHICH connected account of this channel to post as — its @handle or id from list_connector_accounts. Needed only when the brand has more than one bluesky account connected (several and none named is refused by name, never guessed); omit when there is one.'}, 'altText': {'anyOf': [{'type': 'string'}, {'type': 'array', 'items': {'type': 'string'}}], 'description': 'Alt text — an ARRAY, one per image in the same order, or a single STRING to describe every image with it. WRITE ONE: Bluesky’s own lexicon makes `alt` a REQUIRED property of every image, so a post without it is undescribed by design rather than by omission, and Bluesky users expect it. No maximum length is published, so nothing is truncated.'}, 'subject': {'type': 'string', 'description': 'what the post is about'}, 'captions': {'type': 'array', 'items': {'type': 'object', 'propertyNames': {'type': 'string'}, 'additionalProperties': {}}, 'description': "Up to 20 WebVTT caption tracks: [{lang:'en', url:'https://…/en.vtt'}] or [{lang:'en', content:'WEBVTT\\n\\n00:00…'}]. Each file is capped at 20000 bytes."}, 'linkCard': {'anyOf': [{'type': 'boolean'}, {'type': 'object', 'propertyNames': {'type': 'string'}, 'additionalProperties': {}}], 'description': 'Rich link card (`app.bsky.embed.external`). OMIT for the default (a card is built automatically when the post has a URL and no media). `false` never builds one. `true` builds one from the first URL in the text. An object {uri,title,description,thumbUrl} overrides any field — supply BOTH title and description and the page is never fetched. Cannot be combined with imageUrls/videoUrl: a post has ONE embed, so an explicit linkCard beside media is refused.'}, 'videoAlt': {'type': 'string', 'description': 'Alt text describing the video, for accessibility.'}, 'videoUrl': {'type': 'string', 'description': 'One public MP4 URL. Cannot be combined with imageUrls. Bluesky transcodes it, which takes a minute or two.'}, 'imageUrls': {'type': 'array', 'items': {'type': 'string'}, 'description': 'Up to 4 public image URLs to attach. Cannot be combined with videoUrl.'}, 'platformCover': {'type': 'boolean', 'description': 'VIDEO COVER. Bluesky has no cover setting and shows the video’s FIRST frame, so by default Hermoso checks it and, only when that frame is blank (a template ad’s empty opening card), sends Bluesky a copy with the first frame replaced by the video’s best frame. Your Library file is never changed. true = send the file exactly as it is.'}, 'allowDuplicate': {'type': 'boolean', 'description': 'post it even though an identical post was just made'}, 'idempotencyKey': {'type': 'string', 'description': 'any stable string: a repeat within 24h returns the original post instead of posting again'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'hook': {'type': 'string', 'description': 'WHAT ANGLE this post is built on, recorded only at publish time. post_performance ranks hooks on it (a winner needs 5 posts sharing ONE hook), so pass a list_hooks id (e.g. "direct_callout", "before_after") or reuse your own wording EXACTLY across a campaign. Omit it and this post never votes on which hook works.'}, 'link': {'type': 'string', 'description': 'the URL the button opens — not for CALL, and ignored on an OFFER'}, 'brand': {'type': 'string', 'description': "WHICH BRAND this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no brand, or two, is REFUSED and nothing is posted."}, 'title': {'type': 'string', 'description': 'headline — REQUIRED for EVENT and OFFER'}, 'ideaId': {'type': 'string', 'description': 'short id of the content-plan idea this post came from'}, 'recipe': {'type': 'string', 'description': 'the post\'s FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format'}, 'endDate': {'type': 'string', 'description': 'YYYY-MM-DD, defaults to startDate'}, 'subject': {'type': 'string', 'description': 'WHAT THIS POST IS ABOUT — product, feature, offer or theme (e.g. "winter coat", "free trial"). post_performance\'s second grouping axis: reuse the exact wording, as with hook.'}, 'summary': {'type': 'string', 'description': 'the body text of the Post'}, 'imageUrl': {'type': 'string', 'description': 'a Hermoso render image URL (or an upload_file url) to show on the Post'}, 'startDate': {'type': 'string', 'description': 'YYYY-MM-DD — REQUIRED for EVENT and OFFER'}, 'topicType': {'enum': ['STANDARD', 'EVENT', 'OFFER', 'ALERT'], 'type': 'string', 'description': 'default STANDARD'}, 'actionType': {'enum': ['BOOK', 'ORDER', 'SHOP', 'LEARN_MORE', 'SIGN_UP', 'CALL'], 'type': 'string', 'description': 'the button on the Post'}, 'couponCode': {'type': 'string', 'description': 'OFFER only'}, 'locationId': {'type': 'string', 'description': "which listing, e.g. 'locations/123' from list_business_locations — only needed when the account manages more than one"}, 'languageCode': {'type': 'string', 'description': "BCP-47 language of the Post, default 'en'"}, 'redeemOnlineUrl': {'type': 'string', 'description': 'OFFER only — this is the link Google actually uses on an offer'}, 'termsConditions': {'type': 'string', 'description': 'OFFER only'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['text'], 'properties': {'hook': {'type': 'string', 'description': 'WHAT ANGLE this post is built on, recorded only at publish time. post_performance ranks hooks on it (a winner needs 5 posts sharing ONE hook), so pass a list_hooks id (e.g. "direct_callout", "before_after") or reuse your own wording EXACTLY across a campaign. Omit it and this post never votes on which hook works.'}, 'text': {'type': 'string', 'description': 'the post text'}, 'brand': {'type': 'string', 'description': "WHICH BRAND this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no brand, or two, is REFUSED and nothing is posted."}, 'ideaId': {'type': 'string', 'description': 'short id of the content-plan idea this post came from'}, 'recipe': {'type': 'string', 'description': 'the post\'s FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format'}, 'subject': {'type': 'string', 'description': 'WHAT THIS POST IS ABOUT — product, feature, offer or theme (e.g. "winter coat", "free trial"). post_performance\'s second grouping axis: reuse the exact wording, as with hook.'}, 'imageUrl': {'type': 'string', 'description': 'a Hermoso-hosted image URL to attach (≤12MB) — a Hermoso render, or ANY file of the user’s own put through upload_file first. An arbitrary external host is refused (we fetch the bytes ourselves).'}, 'imageUrls': {'type': 'array', 'items': {'type': 'string'}, 'description': "A CAROUSEL IS NOT AVAILABLE ON A PERSONAL PROFILE — LinkedIn's organic multi-image post publishes from a COMPANY PAGE. Passing several here is refused by name rather than posting slide 1; use post_to_linkedin_page instead."}, 'visibility': {'enum': ['PUBLIC', 'CONNECTIONS'], 'type': 'string', 'description': 'default PUBLIC'}, 'allowDuplicate': {'type': 'boolean', 'description': 'post it even though an identical post was just made or attempted. Only pass this when the user genuinely wants the same thing posted twice, or when you have LOOKED at the account and confirmed a timed-out attempt did not land.'}, 'idempotencyKey': {'type': 'string', 'description': 'SAFE RETRIES — any stable string: a repeat of the SAME publish within 24h returns the ORIGINAL post id instead of posting again (an identical publish is also auto-recognised for 10 minutes). On a timeout or an ambiguous error, CALL AGAIN WITH THE SAME KEY: it reports the original post or publishes it once. It never posts twice.'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['text'], 'properties': {'hook': {'type': 'string', 'description': 'WHAT ANGLE this post is built on, recorded only at publish time. post_performance ranks hooks on it (a winner needs 5 posts sharing ONE hook), so pass a list_hooks id (e.g. "direct_callout", "before_after") or reuse your own wording EXACTLY across a campaign. Omit it and this post never votes on which hook works.'}, 'text': {'type': 'string', 'description': 'the post text'}, 'brand': {'type': 'string', 'description': "WHICH BRAND this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no brand, or two, is REFUSED and nothing is posted."}, 'title': {'type': 'string', 'description': 'video title'}, 'ideaId': {'type': 'string', 'description': 'short id of the content-plan idea this post came from'}, 'recipe': {'type': 'string', 'description': 'the post\'s FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format'}, 'altText': {'anyOf': [{'type': 'string'}, {'type': 'array', 'items': {'type': 'string'}}], 'description': 'accessibility alt text (max 4086 characters, ~120 recommended). A STRING describes every image; an ARRAY describes each slide of a multi-image post separately, in slide order — LinkedIn stores altText per image, and their own sample request carries a different one on each. Not available on a PERSONAL-profile post: LinkedIn’s member posting API has no alt-text field at all.'}, 'linkUrl': {'type': 'string', 'description': 'publish a LINK POST — LinkedIn renders a real preview card for this URL instead of leaving a bare link in the text. Mutually exclusive with imageUrl / videoUrl / imageUrls: LinkedIn’s content field is a union, so combining them is refused by name rather than one being dropped.'}, 'subject': {'type': 'string', 'description': 'WHAT THIS POST IS ABOUT — product, feature, offer or theme (e.g. "winter coat", "free trial"). post_performance\'s second grouping axis: reuse the exact wording, as with hook.'}, 'imageUrl': {'type': 'string', 'description': 'a Hermoso-hosted image URL — a render (list_library), or ANY image of the user’s own passed through upload_file first. An arbitrary external host is refused.'}, 'videoUrl': {'type': 'string', 'description': 'a Hermoso-hosted video URL — a render, or the user’s own footage via upload_file. LinkedIn processes it before publishing, which takes a minute.'}, 'coverAtMs': {'type': 'number', 'description': 'THE VIDEO COVER of a videoUrl post, as ONE frame: milliseconds from the start (7000 = the frame at 7s). That frame is uploaded as LinkedIn’s video thumbnail (only possible while the video uploads). videoThumbnailUrl wins.'}, 'imageUrls': {'type': 'array', 'items': {'type': 'string'}, 'description': 'CAROUSEL — an ORDERED list of image (and, where the channel allows, video) URLs published as ONE post the viewer swipes through. THIS IS NOT “post several” — it is a single post with several slides, which is what a multi-slide creative (a listicle, a “1/6 · SWIPE” deck) actually needs; publishing only its first slide tells the viewer to swipe at something that cannot. The ORDER is the product. Limits per channel: Instagram 2–10 (images, videos or a mix), Threads 2–20 (mix allowed), Facebook 2+ (Meta publishes no documented maximum; Hermoso caps the upload fan-out at 30 and says so), LinkedIn company Pages 2–20 (images only), Pinterest 2–5 (images only), TikTok up to 35. One url here is simply an ordinary single post. Anything a channel cannot do is REFUSED with the real reason — nothing is ever quietly downgraded to one slide.'}, 'linkTitle': {'type': 'string', 'description': 'the headline ON the preview card. LINKEDIN NEVER SCRAPES THE PAGE — their Posts API disables URL scraping for API partners outright — so if you do not pass this the card renders UNLABELLED. Fetch the page’s own title and pass it.'}, 'visibility': {'enum': ['PUBLIC', 'CONNECTIONS'], 'type': 'string', 'description': 'default PUBLIC'}, 'captionsSrt': {'type': 'string', 'description': 'CLOSED CAPTIONS for a videoUrl post — the SubRip (.srt) CONTENT itself, cue numbers and `00:00:00,000 --> 00:00:02,000` timing lines included, NOT a URL and NOT the plain script (a file with no timings is refused, because LinkedIn would accept it and then silently never show it). Most of LinkedIn is watched with the sound off, so an uncaptioned video is one most of the feed never hears. LinkedIn allows ONE caption file per video and ENGLISH ONLY; it can be attached only WHILE the video is uploaded, never added to a published post; and it is processed asynchronously, so the reply confirms it was UPLOADED and never that it is visible yet. Requires videoUrl — passing it on an image, carousel or link post is refused by name.'}, 'platformCover': {'type': 'boolean', 'description': 'VIDEO COVER. Omit it (the default) and Hermoso sets the video’s best frame — the same frame as its Library thumbnail — as the cover (LinkedIn’s video thumbnail upload). true = send no cover and let the platform pick (usually the first frame). A cover you pass yourself always wins.'}, 'allowDuplicate': {'type': 'boolean', 'description': 'post it even though an identical post was just made or attempted. Only pass this when the user genuinely wants the same thing posted twice, or when you have LOOKED at the account and confirmed a timed-out attempt did not land.'}, 'idempotencyKey': {'type': 'string', 'description': 'SAFE RETRIES — any stable string: a repeat of the SAME publish within 24h returns the ORIGINAL post id instead of posting again (an identical publish is also auto-recognised for 10 minutes). On a timeout or an ambiguous error, CALL AGAIN WITH THE SAME KEY: it reports the original post or publishes it once. It never posts twice.'}, 'organizationId': {'type': 'string', 'description': 'numeric Page id from list_linkedin_pages'}, 'targetAudience': {'type': 'object', 'properties': {'degrees': {'type': 'array', 'items': {'type': 'string'}}, 'industries': {'type': 'array', 'items': {'type': 'string'}}, 'seniorities': {'type': 'array', 'items': {'type': 'string'}}, 'geoLocations': {'type': 'array', 'items': {'type': 'string'}}, 'jobFunctions': {'type': 'array', 'items': {'type': 'string'}}, 'fieldsOfStudy': {'type': 'array', 'items': {'type': 'string'}}, 'organizations': {'type': 'array', 'items': {'type': 'string'}}, 'staffCountRanges': {'type': 'array', 'items': {'enum': ['SIZE_1', 'SIZE_2_TO_10', 'SIZE_11_TO_50', 'SIZE_51_TO_200', 'SIZE_201_TO_500', 'SIZE_501_TO_1000', 'SIZE_1001_TO_5000', 'SIZE_5001_TO_10000', 'SIZE_10001_OR_MORE'], 'type': 'string'}}}, 'description': 'LINKEDIN COMPANY PAGE POST ONLY — show the post only to Page followers matching these facets (URNs or bare numeric ids; search_linkedin_ads_targeting finds them). LinkedIn requires the matching audience to be over 300 followers and refuses a smaller one. Personal-profile posts cannot be targeted.'}, 'linkDescription': {'type': 'string', 'description': 'the sub-line on the preview card. Same rule as linkTitle: absent means blank, because LinkedIn will not fetch it.'}, 'linkThumbnailUrl': {'type': 'string', 'description': 'a Hermoso-hosted image used as the card’s picture (uploaded to LinkedIn for you). Without it the card has no image.'}, 'videoThumbnailUrl': {'type': 'string', 'description': 'the COVER IMAGE for a videoUrl post — a Hermoso-hosted image (a render, or any picture of the user’s via upload_file). Without it LinkedIn adds a system-generated thumbnail, which on an ad is usually whatever the first frame happens to be. Like captions this can only be set WHILE the video is uploaded, never afterwards. Requires videoUrl. This is NOT linkThumbnailUrl, which is the picture on a link-preview card.'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'hook': {'type': 'string', 'description': 'WHAT ANGLE this post is built on, recorded only at publish time. post_performance ranks hooks on it (a winner needs 5 posts sharing ONE hook), so pass a list_hooks id (e.g. "direct_callout", "before_after") or reuse your own wording EXACTLY across a campaign. Omit it and this post never votes on which hook works.'}, 'link': {'type': 'string', 'description': 'a URL to attach (FB text post only)'}, 'async': {'type': 'boolean', 'description': 'publish in the BACKGROUND and return a job id to poll with get_job, instead of waiting. USE THIS FOR VIDEO: a Facebook or Instagram video publish routinely outlives an agent transport, and a timeout on the synchronous path leaves you unable to tell whether the post is live. With async:true nothing can time out — the job reports the post id and url when it lands.'}, 'brand': {'type': 'string', 'description': "WHICH BRAND this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no brand, or two, is REFUSED and nothing is posted."}, 'place': {'type': 'string', 'description': 'FACEBOOK — tag a location on the Page post. The NUMERIC ID OF THE FACEBOOK PAGE for that place, not a place name and not coordinates.'}, 'story': {'type': 'boolean', 'description': 'INSTAGRAM STORY — publish this as a 24-hour Story instead of a feed post. One image OR one video: Instagram has no carousel story, so a carousel is REFUSED BY NAME rather than quietly posted to the feed. A Story carries NO CAPTION (there is nowhere to show one), no collaborators, no product tags and no trialReel — passing any of those is refused by name and nothing is posted, because a story that silently drops the words is worse than one that never went. INSTAGRAM ONLY: a Facebook Page story is a different upload and is not built, so a Facebook channel with `story` set is refused rather than published to the feed.'}, 'ideaId': {'type': 'string', 'description': 'short id of the content-plan idea this post came from'}, 'pageId': {'type': 'string', 'description': 'target Page id (from list_meta_pages); omit = first Page'}, 'recipe': {'type': 'string', 'description': 'the post\'s FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format'}, 'target': {'enum': ['facebook', 'instagram', 'threads'], 'type': 'string', 'description': 'default facebook; instagram -> the Page’s linked IG; threads -> the brand’s connected Threads account'}, 'account': {'type': 'string', 'description': 'WHICH Instagram account when target is instagram and the brand has several — Page-linked and Instagram Login accounts alike; an @username or id from list_connector_accounts("instagram"). Several and none named is refused by name; omit when there is one.'}, 'altText': {'anyOf': [{'type': 'string'}, {'type': 'array', 'items': {'type': 'string'}}], 'description': 'ACCESSIBILITY — the description screen readers announce, and what the platform otherwise auto-generates badly or not at all. Describe what is actually IN the picture, never the caption. ONE STRING describes the picture; on a CAROUSEL it describes EVERY slide. Pass an ARRAY of strings instead to describe each slide separately, aligned to the slide order — that is strictly better on a multi-slide post, because one sentence read out over six different pictures is wrong for five of them. More descriptions than pictures is refused rather than dropped. WHERE IT LANDS, per Meta’s own docs: INSTAGRAM image posts and the IMAGE slides of an Instagram carousel (up to 1000 characters; dropped rather than sent on a Reel or a video slide, which Meta do not support); FACEBOOK Page photos including every photo of an album, via alt_text_custom (Meta publish no length for it, so nothing is truncated); THREADS on a single-image or single-video post ONLY — Meta document no way to attach alt text to a Threads CAROUSEL slide, so a Threads carousel publishes undescribed and the reply says so rather than risking the whole post on a guess. (Meta’s AI disclosure defaults from PROVENANCE: a Hermoso render is declared is_ai_generated; media that came through upload_file or an external URL, i.e. the user’s own photos or footage, is NOT. Pass `aiGenerated` to force it either way.)'}, 'audioId': {'type': 'string', 'description': 'INSTAGRAM REEL — put one of Instagram’s OWN licensed music tracks under the Reel: the `id` search_instagram_audio returns. Instagram mixes it in when the Reel is published; nothing is downloaded or re-rendered. REELS ONLY, and only on an Instagram account connected through Meta (a Facebook Page with a linked Instagram), which is Instagram’s own rule — the Instagram connector cannot take it and is refused by name.'}, 'message': {'type': 'string', 'description': 'post text / caption'}, 'subject': {'type': 'string', 'description': 'WHAT THIS POST IS ABOUT — product, feature, offer or theme (e.g. "winter coat", "free trial"). post_performance\'s second grouping axis: reuse the exact wording, as with hook.'}, 'audience': {'type': 'object', 'properties': {'cities': {'type': 'array', 'items': {'type': 'string'}, 'description': 'Meta location keys for cities'}, 'minAge': {'anyOf': [{'type': 'number', 'const': 13}, {'type': 'number', 'const': 15}, {'type': 'number', 'const': 18}, {'type': 'number', 'const': 21}, {'type': 'number', 'const': 25}]}, 'regions': {'type': 'array', 'items': {'type': 'string'}, 'description': 'Meta location keys for regions/states'}, 'countries': {'type': 'array', 'items': {'type': 'string'}, 'description': 'two-letter codes, e.g. ["CA","US"]'}}, 'description': 'FACEBOOK PAGE POST ONLY — limit who can see this organic post to people in these places and/or over this age (Facebook allows 13, 15, 18, 21 or 25). Not on Instagram, Threads or a Facebook Reel (a vertical clip becomes a Reel): those are refused. Region and city keys come from the Meta ads location search.'}, 'coverUrl': {'type': 'string', 'description': 'INSTAGRAM REEL COVER — a public image url Instagram fetches and uses as the cover in the Reels tab. REELS ONLY, and the alternative to `thumbOffset`: passing both is refused, since they are two answers to the same question. Run a local file through upload_file first.'}, 'imageUrl': {'type': 'string', 'description': 'public https URL, a data: URI, or a Hermoso /generated path (upload_file gives you one for a local file)'}, 'linkName': {'type': 'string', 'description': 'FACEBOOK — override the HEADLINE of the link preview. Without it the post shows whatever Open Graph title the destination carries, which on a bare landing page is often nothing. Needs `link`. MEASURED 2026-09-17: Facebook only honours these when the Page’s business has VERIFIED the domain (Business Manager > Brand safety > Domains) — without that Meta refuses the post outright with “Only owners of the URL…”, so leave them out unless the brand owns and has verified that link’s domain.'}, 'topicTag': {'type': 'string', 'description': 'THREADS ONLY — one topic tag for discovery, 1–50 characters. A leading # is stripped for you; Threads refuses "." and "&".'}, 'videoUrl': {'type': 'string', 'description': 'public https URL, data: URI, or /generated path — FB video post / IG Reel'}, 'audioName': {'type': 'string', 'description': 'INSTAGRAM REEL — the name of the Reel’s audio track, which is what viewers tap through to. REELS ONLY.'}, 'coverAtMs': {'type': 'number', 'description': 'THE VIDEO COVER for every target of this post, as ONE frame: milliseconds from the start (7000 = the frame at 7s). Instagram gets it as thumb_offset, Facebook as its uploaded cover (a Reel’s preferred thumbnail). Instagram’s own thumbOffset, or a Hermoso-hosted coverUrl, also becomes the Facebook cover.'}, 'imageUrls': {'type': 'array', 'items': {'type': 'string'}, 'description': 'CAROUSEL — an ORDERED list of image (and, where the channel allows, video) URLs published as ONE post the viewer swipes through. THIS IS NOT “post several” — it is a single post with several slides, which is what a multi-slide creative (a listicle, a “1/6 · SWIPE” deck) actually needs; publishing only its first slide tells the viewer to swipe at something that cannot. The ORDER is the product. Limits per channel: Instagram 2–10 (images, videos or a mix), Threads 2–20 (mix allowed), Facebook 2+ (Meta publishes no documented maximum; Hermoso caps the upload fan-out at 30 and says so), LinkedIn company Pages 2–20 (images only), Pinterest 2–5 (images only), TikTok up to 35. One url here is simply an ordinary single post. Anything a channel cannot do is REFUSED with the real reason — nothing is ever quietly downgraded to one slide.'}, 'trialReel': {'enum': ['MANUAL', 'SS_PERFORMANCE'], 'type': 'string', 'description': 'INSTAGRAM TRIAL REEL — publish this Reel to NON-FOLLOWERS ONLY at first (Instagram allows trials only on accounts above its follower threshold — about 1,000 followers; an ineligible account is refused by name and nothing is posted), so a hook can be tested on a cold audience without spending it on the people who already follow the brand. Instagram then shows it to followers only if it graduates. MANUAL = the creator graduates it by hand in the Instagram app; SS_PERFORMANCE = Instagram graduates it automatically if it performs well. REELS ONLY and INSTAGRAM ONLY: an image, a carousel, a Facebook post or a Threads post is REFUSED BY NAME rather than quietly published as an ordinary post — a trial that silently goes to every follower is the exact opposite of what was asked for. Omit it for a normal Reel.'}, 'locationId': {'type': 'string', 'description': 'TAG A PLACE. THREADS: a place id from search_threads_locations. INSTAGRAM: the NUMERIC ID OF A FACEBOOK PAGE associated with that location, not a place name and not coordinates; a non-numeric value is refused rather than sent.'}, 'scheduleAt': {'type': 'string', 'description': 'FACEBOOK ONLY — schedule instead of posting now. ISO timestamp (2026-08-01T09:00:00Z) or unix seconds; must be 10 minutes to 30 days ahead. Facebook holds the post and publishes it at that time, so nothing has to stay running on our side. Instagram and Threads have NO scheduling in Meta’s API — passing this for them is refused rather than silently posted immediately.'}, 'aiGenerated': {'type': 'boolean', 'description': 'INSTAGRAM / FACEBOOK REEL — Meta’s is_ai_generated self-disclosure. OMIT IT and Hermoso decides from provenance: a Hermoso render is declared, media that came through upload_file or from an external URL (the user’s own photographs or footage) is NOT — a real photo must never carry Instagram’s “AI info” label. Pass true or false only to override: false strips the label from something Hermoso would otherwise declare, true declares a render the user uploaded themselves.'}, 'audioVolume': {'type': 'integer', 'maximum': 100, 'minimum': 0, 'description': 'INSTAGRAM REEL — how loud that track plays, 0–100 (Instagram’s default 100). Needs audioId.'}, 'linkPicture': {'type': 'string', 'description': 'FACEBOOK — override the IMAGE of the link preview: a public http(s) url Facebook fetches itself. Needs `link`. MEASURED 2026-09-17: Facebook only honours these when the Page’s business has VERIFIED the domain (Business Manager > Brand safety > Domains) — without that Meta refuses the post outright with “Only owners of the URL…”, so leave them out unless the brand owns and has verified that link’s domain.'}, 'productTags': {'type': 'array', 'items': {}, 'description': 'INSTAGRAM SHOPPING — make the post SHOPPABLE by tagging products from the brand’s own catalog; tapping a tag opens the product’s price sheet inside Instagram. Instagram only. ON A PHOTO each tag is {product_id, x, y}, where x and y are FRACTIONS of the image from 0.0 (left/top) to 1.0 (right/bottom) — 0.5,0.5 is the middle — and BOTH are required, max 20. ON A REEL it is {product_id} ALONE with no coordinates, max 30. ON A CAROUSEL it is an array PER SLIDE ([[{…}], [], [{…}]]) because Instagram tags each slide’s own container, max 5 per slide and 20 across the post. Ids come from search_instagram_shopping_products; call list_instagram_shopping_catalogs FIRST, because tagging needs an APPROVED Instagram Shop and without one this fails after the media is already uploaded. A tag whose product is not “approved” is stored and shown to nobody.'}, 'quotePostId': {'type': 'string', 'description': 'THREADS ONLY — the id of a post to QUOTE. A quote post is a distinct post type, not a formatting option.'}, 'shareToFeed': {'type': 'boolean', 'description': 'INSTAGRAM REEL — true puts the Reel in the Feed grid as well as the Reels tab. REELS ONLY. Left unset it follows Instagram’s own default; Hermoso does not flip it either way on the user’s behalf.'}, 'thumbOffset': {'type': 'number', 'description': 'INSTAGRAM REEL COVER, the other way — which frame becomes the cover, in MILLISECONDS from the start of the video. REELS ONLY. Use it instead of `coverUrl` when the right cover is already a frame of the clip.'}, 'videoVolume': {'type': 'integer', 'maximum': 100, 'minimum': 0, 'description': 'INSTAGRAM REEL — how loud the video’s own sound plays under the track, 0–100 (Instagram’s default 100; 0 = the track alone). Needs audioId.'}, 'callToAction': {'enum': ['BOOK_TRAVEL', 'BUY_NOW', 'CALL_NOW', 'DOWNLOAD', 'GET_DIRECTIONS', 'LEARN_MORE', 'LIKE_PAGE', 'MESSAGE_PAGE', 'NO_BUTTON', 'OPEN_LINK', 'SHOP_NOW', 'SIGN_UP', 'WATCH_MORE'], 'type': 'string', 'description': 'FACEBOOK — a real call-to-action BUTTON on the Page post. The types that open a destination (SHOP_NOW, LEARN_MORE, SIGN_UP, BUY_NOW, DOWNLOAD, OPEN_LINK, WATCH_MORE, BOOK_TRAVEL) need somewhere to go: the post’s own `link`, or `callToActionLink`. CALL_NOW, MESSAGE_PAGE, LIKE_PAGE and GET_DIRECTIONS act on the Page itself and take none.'}, 'countryCodes': {'type': 'array', 'items': {'type': 'string'}, 'description': 'THREADS ONLY — restrict the post to these ISO 3166-1 alpha-2 countries. Warning: This is an ALLOWLIST, so the post becomes INVISIBLE in every country not named — it narrows reach, it does not target.'}, 'replyControl': {'enum': ['everyone', 'accounts_you_follow', 'mentioned_only', 'parent_post_author_only', 'followers_only'], 'type': 'string', 'description': 'THREADS ONLY — who may reply. Default is everyone.'}, 'collaborators': {'type': 'array', 'items': {'type': 'string'}, 'description': 'INSTAGRAM COLLAB — up to 3 Instagram usernames invited to CO-AUTHOR this post. Once one accepts, the post appears on THEIR profile too, with both handles in the header and the likes and comments shared — it is how a brand reaches a creator’s audience without paying for placement, and it is the single most-asked thing a scheduler normally cannot do. Pass handles only ("hermosoai"), not profile links; a leading @ is fine. INSTAGRAM ONLY — Facebook and Threads have no collab post at all and are refused BY NAME rather than silently dropping the co-authors — and never on a Story. AN INVITE IS NOT A CO-POST: publishing SENDS a request the other account must accept in their Instagram notifications, and until they do the post is on this brand’s profile ALONE; they may also decline, and Instagram sends no notification either way. So never report the post as live on both accounts — read the invite status back out of the reply, and use instagram_collaborators later to find out whether they accepted.'}, 'platformCover': {'type': 'boolean', 'description': 'VIDEO COVER. Omit it (the default) and Hermoso sets the video’s best frame — the same frame as its Library thumbnail — as the cover (Instagram Reel: thumb_offset; Facebook video/Reel: an uploaded cover image) — and on Threads, which has no cover setting, a blank first frame is replaced on a copy sent to Threads only. true = send no cover and let the platform pick (usually the first frame). A cover you pass yourself always wins.'}, 'allowDuplicate': {'type': 'boolean', 'description': 'post it even though an identical post was just made or attempted. Only pass this when the user genuinely wants the same thing posted twice, or when you have LOOKED at the account and confirmed a timed-out attempt did not land.'}, 'idempotencyKey': {'type': 'string', 'description': 'SAFE RETRIES — any stable string: a repeat of the SAME publish within 24h returns the ORIGINAL post id instead of posting again (an identical publish is also auto-recognised for 10 minutes). On a timeout or an ambiguous error, CALL AGAIN WITH THE SAME KEY: it reports the original post or publishes it once. It never posts twice.'}, 'linkAttachment': {'type': 'string', 'description': 'THREADS TEXT POSTS ONLY — a full http(s) URL rendered as a clickable link card. This is how a Threads post carries a destination at all; without it a link is just text. Meta allows at most 5 links per post and refuses a link attachment on a post that also carries an image or video, so Hermoso refuses that combination up front rather than letting Meta silently drop it.'}, 'linkDescription': {'type': 'string', 'description': 'FACEBOOK — override the DESCRIPTION of the link preview. Needs `link`. MEASURED 2026-09-17: Facebook only honours these when the Page’s business has VERIFIED the domain (Business Manager > Brand safety > Domains) — without that Meta refuses the post outright with “Only owners of the URL…”, so leave them out unless the brand owns and has verified that link’s domain.'}, 'paidPartnership': {'type': 'boolean', 'description': 'INSTAGRAM — the PAID PARTNERSHIP label. A COMPLIANCE DECLARATION, the same kind Hermoso already carries for TikTok and X: set it whenever the post is sponsored, gifted or otherwise paid for. Opt-in and never inferred — it is the poster’s own statement about their commercial relationship.'}, 'callToActionLink': {'type': 'string', 'description': 'FACEBOOK — where the button goes, when that is not the post’s own `link`.'}, 'crossreshareToIg': {'type': 'boolean', 'description': 'THREADS ONLY — ALSO share this Threads post to the linked Instagram account AS A STORY (not a feed post), in the same publish. NOT available on a Threads CAROUSEL, which is refused by name rather than silently dropped. THERE IS NO CONFIRMATION: Threads returns no field saying whether the Story was created, so report it as REQUESTED and tell the user to check their Instagram Stories — never that it is live.'}, 'crossreshareDarkMode': {'type': 'boolean', 'description': 'THREADS ONLY — render that Instagram Story in dark mode. Only meaningful alongside crossreshareToIg; on its own it is refused rather than silently ignored, because a parameter that never reaches the wire must not look accepted.'}, 'brandedContentSponsorIds': {'type': 'array', 'items': {'type': 'string'}, 'description': 'INSTAGRAM — the numeric Instagram USER IDS of the brands behind that label (at most 2, and ids rather than @handles). Naming sponsors IS asking for the label, so setting these with `paidPartnership:false` is refused instead of publishing brand credits with no disclosure.'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['boardId'], 'properties': {'hook': {'type': 'string', 'description': 'WHAT ANGLE this post is built on, recorded only at publish time. post_performance ranks hooks on it (a winner needs 5 posts sharing ONE hook), so pass a list_hooks id (e.g. "direct_callout", "before_after") or reuse your own wording EXACTLY across a campaign. Omit it and this post never votes on which hook works.'}, 'link': {'type': 'string', 'description': 'destination URL the Pin clicks through to'}, 'brand': {'type': 'string', 'description': "WHICH BRAND this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no brand, or two, is REFUSED and nothing is posted."}, 'title': {'type': 'string', 'description': 'Pin title, max 100 characters'}, 'ideaId': {'type': 'string', 'description': 'short id of the content-plan idea this post came from'}, 'recipe': {'type': 'string', 'description': 'the post\'s FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format'}, 'account': {'type': 'string', 'description': 'WHICH connected account of this channel to post as — its @handle or id from list_connector_accounts. Needed only when the brand has more than one pinterest account connected (several and none named is refused by name, never guessed); omit when there is one.'}, 'altText': {'anyOf': [{'type': 'string'}, {'type': 'array', 'items': {'type': 'string'}}], 'description': 'accessibility alt text, max 500 characters. PIN-LEVEL: Pinterest’s API has no per-item alt text at all, so on a CAROUSEL the FIRST description is used for the whole Pin and the reply states that the others were not sent.'}, 'boardId': {'type': 'string', 'description': 'numeric board id from list_pinterest_boards — the user picks it, never guess'}, 'subject': {'type': 'string', 'description': 'WHAT THIS POST IS ABOUT — product, feature, offer or theme (e.g. "winter coat", "free trial"). post_performance\'s second grouping axis: reuse the exact wording, as with hook.'}, 'imageUrl': {'type': 'string', 'description': 'a Hermoso render image URL (or an upload_file url)'}, 'videoUrl': {'type': 'string', 'description': 'a Hermoso render video URL — takes 1–2 minutes to ingest'}, 'coverAtMs': {'type': 'number', 'description': 'THE VIDEO COVER of a video Pin, as ONE frame: milliseconds from the start (7000 = the frame at 7s). That frame is cut and sent as the Pin cover image. coverImageUrl wins.'}, 'imageUrls': {'type': 'array', 'items': {'type': 'string'}, 'description': 'CAROUSEL — an ORDERED list of image (and, where the channel allows, video) URLs published as ONE post the viewer swipes through. THIS IS NOT “post several” — it is a single post with several slides, which is what a multi-slide creative (a listicle, a “1/6 · SWIPE” deck) actually needs; publishing only its first slide tells the viewer to swipe at something that cannot. The ORDER is the product. Limits per channel: Instagram 2–10 (images, videos or a mix), Threads 2–20 (mix allowed), Facebook 2+ (Meta publishes no documented maximum; Hermoso caps the upload fan-out at 30 and says so), LinkedIn company Pages 2–20 (images only), Pinterest 2–5 (images only), TikTok up to 35. One url here is simply an ordinary single post. Anything a channel cannot do is REFUSED with the real reason — nothing is ever quietly downgraded to one slide.'}, 'slideText': {'type': 'array', 'items': {'type': 'object', 'properties': {'link': {'type': 'string'}, 'title': {'type': 'string'}, 'description': {'type': 'string'}}}, 'description': 'PINTEREST CAROUSEL ONLY — per-slide title, description and destination LINK, one object per slide in slide order. This is the ONLY genuine per-slide caption on any channel Hermoso publishes to: slide 4 can send people to the product ON slide 4, where every other platform gives a carousel one shared caption. Omit any field to leave it unset; the Pin’s own title/description/link still describe the Pin as a whole. Pinterest publishes no length limit on these, so nothing is truncated. More entries than slides is refused rather than dropped.'}, 'description': {'type': 'string', 'description': 'Pin description, max 800 characters — this is what Pinterest search reads'}, 'coverImageUrl': {'type': 'string', 'description': 'video Pins only — a render to use as the cover frame'}, 'platformCover': {'type': 'boolean', 'description': 'VIDEO COVER. Omit it (the default) and Hermoso sets the video’s best frame — the same frame as its Library thumbnail — as the cover (on Pinterest the frame rides as the cover image). true = send no cover and let the platform pick (usually the first frame). A cover you pass yourself always wins.'}, 'allowDuplicate': {'type': 'boolean', 'description': 'post it even though an identical post was just made or attempted. Only pass this when the user genuinely wants the same thing posted twice, or when you have LOOKED at the account and confirmed a timed-out attempt did not land.'}, 'boardSectionId': {'type': 'string', 'description': 'optional section within the board'}, 'idempotencyKey': {'type': 'string', 'description': 'SAFE RETRIES — any stable string: a repeat of the SAME publish within 24h returns the ORIGINAL post id instead of posting again (an identical publish is also auto-recognised for 10 minutes). On a timeout or an ambiguous error, CALL AGAIN WITH THE SAME KEY: it reports the original post or publishes it once. It never posts twice.'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['chatId'], 'properties': {'hook': {'type': 'string', 'description': "the post's angle — a list_hooks id or your own wording, reused exactly"}, 'text': {'type': 'string', 'description': 'the message. ≤4096 characters on its own; ≤1024 once any image or video is attached.'}, 'brand': {'type': 'string', 'description': "WHICH BRAND this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no brand, or two, is REFUSED and nothing is posted."}, 'chatId': {'type': 'string', 'description': "REQUIRED — the destination: a public channel's @username, or the numeric chat id. Never guessed; ask the user, or use list_telegram_chats."}, 'ideaId': {'type': 'string', 'description': 'short id of the content-plan idea this post came from'}, 'recipe': {'type': 'string', 'description': 'the post\'s FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format'}, 'silent': {'type': 'boolean', 'description': 'deliver without a notification sound (Telegram’s disable_notification). This is NOT a visibility setting — the message is just as visible.'}, 'account': {'type': 'string', 'description': 'WHICH connected account of this channel to post as — its @handle or id from list_connector_accounts. Needed only when the brand has more than one telegram account connected (several and none named is refused by name, never guessed); omit when there is one.'}, 'subject': {'type': 'string', 'description': 'what the post is about'}, 'imageUrl': {'type': 'string', 'description': 'one image (≤10MB after upload)'}, 'videoUrl': {'type': 'string', 'description': 'one video (≤50MB). Passed alongside imageUrls it joins the album as one more item.'}, 'coverAtMs': {'type': 'number', 'description': 'THE VIDEO COVER in the chat, as ONE frame: milliseconds from the start (7000 = the frame at 7s). Sent as Telegram’s cover image; beats platformCover.'}, 'imageUrls': {'type': 'array', 'items': {'type': 'string'}, 'description': 'an ALBUM of 2–10 media, in order. Photos and videos may be mixed — Telegram allows it.'}, 'platformCover': {'type': 'boolean', 'description': 'VIDEO COVER. Omit it (the default) and Hermoso sets the video’s best frame — the same frame as its Library thumbnail — as the cover (Telegram’s in-chat video cover). true = send no cover and let the platform pick (usually the first frame). A cover you pass yourself always wins.'}, 'allowDuplicate': {'type': 'boolean', 'description': 'post it even though an identical post was just made'}, 'disablePreview': {'type': 'boolean', 'description': 'suppress the link-preview card on a text-only post. Default is Telegram’s own behaviour (previews on).'}, 'idempotencyKey': {'type': 'string', 'description': 'any stable string: a repeat within 24h returns the original post instead of posting again'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'hook': {'type': 'string', 'description': 'WHAT ANGLE this post is built on, recorded only at publish time. post_performance ranks hooks on it (a winner needs 5 posts sharing ONE hook), so pass a list_hooks id (e.g. "direct_callout", "before_after") or reuse your own wording EXACTLY across a campaign. Omit it and this post never votes on which hook works.'}, 'brand': {'type': 'string', 'description': "WHICH BRAND this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no brand, or two, is REFUSED and nothing is posted."}, 'title': {'type': 'string', 'description': 'the caption — hashtags go here (video ≤2200 chars, photo post ≤4000)'}, 'ideaId': {'type': 'string', 'description': 'short id of the content-plan idea this post came from'}, 'recipe': {'type': 'string', 'description': 'the post\'s FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format'}, 'account': {'type': 'string', 'description': 'WHICH connected account of this channel to post as — its @handle or id from list_connector_accounts. Needed only when the brand has more than one tiktok account connected (several and none named is refused by name, never guessed); omit when there is one.'}, 'privacy': {'enum': ['PUBLIC_TO_EVERYONE', 'MUTUAL_FOLLOW_FRIENDS', 'FOLLOWER_OF_CREATOR', 'SELF_ONLY'], 'type': 'string', 'description': 'REQUIRED for destination:"post", for photos and video alike. Must be one the creator actually allows — read them from tiktok_creator_info, never guess.'}, 'subject': {'type': 'string', 'description': 'WHAT THIS POST IS ABOUT — product, feature, offer or theme (e.g. "winter coat", "free trial"). post_performance\'s second grouping axis: reuse the exact wording, as with hook.'}, 'videoUrl': {'type': 'string', 'description': 'the video to post — a Hermoso render URL or an upload_file url. Omit for a photo post.'}, 'imageUrls': {'type': 'array', 'items': {'type': 'string'}, 'description': 'a PHOTO POST: 1–35 image URLs in slide order. One url = a single-image photo post. Do not combine with videoUrl.'}, 'yourBrand': {'type': 'boolean', 'description': 'discloses that this promotes the creator’s own brand'}, 'coverIndex': {'type': 'number', 'description': 'photo posts: which slide is the cover, 0-based. Default 0 (the first slide).'}, 'photoTitle': {'type': 'string', 'description': 'photo posts only: a short title above the caption (≤90 chars). Defaults to the caption’s first line.'}, 'aiGenerated': {'type': 'boolean', 'description': 'TikTok’s is_aigc AI-generated-content label (video posts). OMIT IT and Hermoso decides from provenance: a Hermoso render is declared, a video that came through upload_file or an external URL (the user’s own footage) is NOT. true/false overrides.'}, 'destination': {'enum': ['post', 'draft'], 'type': 'string', 'description': '"post" (default) = live on the profile now (needs the privacy the user chose + their explicit yes); "draft" = to their TikTok inbox for them to finish and publish in the app — only when they ask for a draft or want to add a TikTok sound, and only after telling them so.'}, 'disableDuet': {'type': 'boolean', 'description': 'video only — TikTok has no duet on a photo post'}, 'autoAddMusic': {'type': 'boolean', 'description': 'photo posts only: let TikTok add a recommended track (default true — a silent slideshow reads as broken)'}, 'disableStitch': {'type': 'boolean', 'description': 'video only — TikTok has no stitch on a photo post'}, 'platformCover': {'type': 'boolean', 'description': 'VIDEO COVER. Omit it (the default) and Hermoso sets the video’s best frame — the same frame as its Library thumbnail — as the cover (TikTok video_cover_timestamp_ms, on a direct post — a draft takes no cover, you pick it in the TikTok app). true = send no cover and let the platform pick (usually the first frame). A cover you pass yourself always wins.'}, 'brandedContent': {'type': 'boolean', 'description': 'discloses a paid partnership — cannot be combined with SELF_ONLY privacy'}, 'disableComment': {'type': 'boolean'}, 'coverTimestampMs': {'type': 'number', 'description': 'video only: which frame to use as the cover, in ms'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'hook': {'type': 'string', 'description': 'WHAT ANGLE this post is built on, recorded only at publish time. post_performance ranks hooks on it (a winner needs 5 posts sharing ONE hook), so pass a list_hooks id (e.g. "direct_callout", "before_after") or reuse your own wording EXACTLY across a campaign. Omit it and this post never votes on which hook works.'}, 'poll': {'type': 'object', 'required': ['options'], 'properties': {'options': {'type': 'array', 'items': {'type': 'string'}, 'description': '2-4 choices, max 25 characters each'}, 'durationMinutes': {'type': 'number', 'description': '5 to 10080 minutes (7 days); default 1440 = one day'}}, 'description': 'run a poll on the post. X does not allow a poll and media on the same post, and a poll cannot ride on a thread.'}, 'text': {'type': 'string', 'description': 'the post text. 280 characters without X Premium, up to 25,000 with it — write the full thing, it is never truncated. Use this OR thread, not both.'}, 'brand': {'type': 'string', 'description': "WHICH BRAND this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no brand, or two, is REFUSED and nothing is posted."}, 'ideaId': {'type': 'string', 'description': 'short id of the content-plan idea this post came from'}, 'recipe': {'type': 'string', 'description': 'the post\'s FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format'}, 'thread': {'type': 'array', 'items': {'type': 'string'}, 'description': 'a thread: each string is one post, published in order, each replying to the previous. Max 25. Each part follows the same length rule as `text`, and on an X Premium account ONE long post is usually both better reading and cheaper than a thread.'}, 'account': {'type': 'string', 'description': 'WHICH connected account of this channel to post as — its @handle or id from list_connector_accounts. Needed only when the brand has more than one x account connected (several and none named is refused by name, never guessed); omit when there is one.'}, 'altText': {'anyOf': [{'type': 'string'}, {'type': 'array', 'items': {'type': 'string'}}], 'description': 'accessibility description of the attached media, max 1000 characters — write one whenever you attach a render. Costs a small extra amount: X bills one metadata write PER media. With SEVERAL media, pass an ARRAY aligned to their order — X attaches alt text per media id, and a single string describes only the FIRST one (X renders up to four media as a GRID, not a carousel, so one sentence would be wrong for the other three).'}, 'subject': {'type': 'string', 'description': 'WHAT THIS POST IS ABOUT — product, feature, offer or theme (e.g. "winter coat", "free trial"). post_performance\'s second grouping axis: reuse the exact wording, as with hook.'}, 'imageUrl': {'type': 'string', 'description': 'alias of mediaUrl for an IMAGE — same as passing it as mediaUrl'}, 'mediaUrl': {'type': 'string', 'description': 'a Hermoso render (image or video) to attach to the first post — pass its served URL, or an upload_file url for external media'}, 'videoUrl': {'type': 'string', 'description': 'alias of mediaUrl for a VIDEO — same as passing it as mediaUrl'}, 'mediaUrls': {'type': 'array', 'items': {'type': 'string'}, 'description': 'UP TO FOUR Hermoso-hosted media attached to ONE post — X’s own schema caps media_ids at 4. X renders them as a GRID: every image visible at once, nothing to swipe to. That is NOT a carousel, and a numbered "1/6 · SWIPE" slide deck must still not be sent here — it would publish as a grid and the "swipe" instruction would make no sense. Order decides the layout. On a thread the media rides the FIRST post; give each later part its own post to attach more. Mutually exclusive with mediaUrl, and more than 4 is refused before anything is uploaded.'}, 'replyToId': {'type': 'string', 'description': 'numeric id of an existing X post to reply to. X ONLY ACCEPTS AN API REPLY TO A POST THAT MENTIONS THIS ACCOUNT OR THAT THIS ACCOUNT WROTE (X policy since Feb 2026, every tier below Enterprise): a cold reply under a stranger\'s post is refused by X with "You can only reply to or quote posts where you are mentioned or are the author" and nothing is posted. Reply to those from x.com itself; use this for replies in your own threads and to people who tagged you.'}, 'communityId': {'type': 'string', 'description': 'publish into an X COMMUNITY instead of the main timeline — the number in the community’s own URL (x.com/i/communities/<id>). The connected account must be a MEMBER of it; X answers a non-member and a non-existent id with the same refusal and does not separate them.'}, 'quotePostId': {'type': 'string', 'description': 'numeric id of a post to QUOTE — X renders the quoted post inside yours. This is NOT replyToId: a reply sits under the original in its thread, a quote stands alone on your own timeline with the original embedded, which is the one you want for commentary. X makes a quote mutually exclusive with media and with a poll (their own schema), and a quote is billed at the higher LINK rate because X appends the quoted post’s t.co URL whatever your text says. Same X rule as replyToId: a quote of a post that does not mention this account is refused by X.'}, 'platformCover': {'type': 'boolean', 'description': 'VIDEO COVER. X has no cover setting and shows the video’s FIRST frame, so by default Hermoso checks it and, only when that frame is blank (a template ad’s empty opening card), sends X a copy with the first frame replaced by the video’s best frame. Your Library file is never changed. true = send the file exactly as it is.'}, 'replySettings': {'enum': ['following', 'mentionedUsers', 'subscribers', 'verified'], 'type': 'string', 'description': 'restrict who can reply — omit for everyone, which is the right default for a brand post'}, 'paidPartnership': {'type': 'boolean', 'description': 'label the post a PAID PARTNERSHIP on X — the same disclosure Hermoso already ships for TikTok. OPT-IN ONLY: set it when the post is sponsored, gifted or otherwise paid for, and never assume it on the user’s behalf.'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['videoUrl'], 'properties': {'hook': {'type': 'string', 'description': 'WHAT ANGLE this post is built on, recorded only at publish time. post_performance ranks hooks on it (a winner needs 5 posts sharing ONE hook), so pass a list_hooks id (e.g. "direct_callout", "before_after") or reuse your own wording EXACTLY across a campaign. Omit it and this post never votes on which hook works.'}, 'tags': {'type': 'array', 'items': {'type': 'string'}, 'description': 'up to 30 tags'}, 'brand': {'type': 'string', 'description': "WHICH BRAND this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no brand, or two, is REFUSED and nothing is posted."}, 'title': {'type': 'string', 'description': 'REQUIRED in practice — the headline every search result and the Shorts feed shows (≤100 chars); an upload with no title is refused'}, 'ideaId': {'type': 'string', 'description': 'short id of the content-plan idea this post came from'}, 'recipe': {'type': 'string', 'description': 'the post\'s FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format'}, 'account': {'type': 'string', 'description': 'WHICH connected account of this channel to post as — its @handle or id from list_connector_accounts. Needed only when the brand has more than one youtube account connected (several and none named is refused by name, never guessed); omit when there is one.'}, 'privacy': {'enum': ['private', 'unlisted', 'public'], 'type': 'string', 'description': 'default public (live + searchable on the channel); unlisted = link-only (the ad-ready setting, or to add YouTube music in Studio first — only when the user asks); private = eyes-only (cannot run as an ad)'}, 'subject': {'type': 'string', 'description': 'WHAT THIS POST IS ABOUT — product, feature, offer or theme (e.g. "winter coat", "free trial"). post_performance\'s second grouping axis: reuse the exact wording, as with hook.'}, 'videoUrl': {'type': 'string', 'description': 'the video to post — a Hermoso render URL or an upload_file url'}, 'coverAtMs': {'type': 'number', 'description': 'THE VIDEO COVER (the custom thumbnail), as ONE frame: milliseconds from the start (7000 = the frame at 7s). That frame is cut from the upload and set with thumbnails.set. thumbnailUrl wins; this beats platformCover.'}, 'publishAt': {'type': 'string', 'description': 'SCHEDULE the publish — ISO 8601, e.g. "2026-09-01T15:00:00Z", and it must be in the future. YouTube only allows this on a PRIVATE video and makes it PUBLIC at that moment, so pass privacy:"private" (or leave privacy unset) — asking for a scheduled "unlisted" or "public" post is refused rather than half-honoured.'}, 'categoryId': {'type': 'string', 'description': 'YouTube category id, NUMERIC — default "22" (People & Blogs), which is wrong for most ads. 1 Film & Animation · 2 Autos & Vehicles · 10 Music · 15 Pets & Animals · 17 Sports · 19 Travel & Events · 20 Gaming · 22 People & Blogs · 23 Comedy · 24 Entertainment · 25 News & Politics · 26 Howto & Style · 27 Education · 28 Science & Technology · 29 Nonprofits & Activism. The assignable set is region-specific, so the value is forwarded as given and YouTube has the last word.'}, 'aiGenerated': {'type': 'boolean', 'description': 'YouTube’s “altered or synthetic content” declaration (containsSyntheticMedia). OMIT IT and Hermoso decides from provenance: a Hermoso render is declared, a video the user uploaded through upload_file or from an external URL is NOT — real footage must not carry the label. true/false overrides.'}, 'description': {'type': 'string', 'description': 'REQUIRED in practice — what the video shows, a line about the brand, a link and a few #hashtags (≤5000 chars); YouTube search, suggested videos and the Shorts feed rank on it, so an upload with no description (and no caption) is refused'}, 'thumbnailUrl': {'type': 'string', 'description': 'the custom thumbnail: a Hermoso-hosted image (make_thumbnail, or upload_file for the user’s own). Omit it and a representative frame of the video is set, free; "auto" keeps YouTube’s pick. Custom thumbnails need a verified channel.'}, 'platformCover': {'type': 'boolean', 'description': 'VIDEO COVER. Omit it (the default) and Hermoso sets the video’s best frame — the same frame as its Library thumbnail — as the cover (YouTube custom thumbnail; the same as thumbnailUrl:"auto" when true). true = send no cover and let the platform pick (usually the first frame). A cover you pass yourself always wins.'}, 'notifySubscribers': {'type': 'boolean', 'description': 'THE DEFAULT FOLLOWS PRIVACY. privacy:"public" NOTIFIES the channel\'s subscribers — that is YouTube\'s own default and normally what someone publishing publicly wants. privacy:"unlisted" and "private" do NOT: the video is not on the channel, so announcing it is nonsense, and a blast to somebody\'s whole subscriber list cannot be undone. A scheduled publish (publishAt) is PRIVATE at upload, so it does not notify either — pass true to announce one. An explicit value ALWAYS wins in both directions: true announces an unlisted/private upload, false publishes publicly and quietly. The reply reports which way it went and why.'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['title', 'body'], 'properties': {'body': {'type': 'string', 'description': 'the article body, as markdown or plain prose. Markdown headings, lists, quotes, links, emphasis, ``` code fences and | pipe | tables | are all converted to X’s own Article structure.'}, 'hook': {'type': 'string', 'description': 'WHAT ANGLE this post is built on, recorded only at publish time. post_performance ranks hooks on it (a winner needs 5 posts sharing ONE hook), so pass a list_hooks id (e.g. "direct_callout", "before_after") or reuse your own wording EXACTLY across a campaign. Omit it and this post never votes on which hook works.'}, 'brand': {'type': 'string', 'description': "WHICH BRAND this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no brand, or two, is REFUSED and nothing is posted."}, 'title': {'type': 'string', 'description': 'the Article title — X requires one and refuses a draft without it. This is what shows on the timeline card.'}, 'ideaId': {'type': 'string', 'description': 'short id of the content-plan idea this post came from'}, 'recipe': {'type': 'string', 'description': 'the post\'s FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format'}, 'publish': {'type': 'boolean', 'description': 'default true. Pass false to save it as a DRAFT in the account’s X Articles composer instead — nothing becomes public, the user can review and publish it from X, and it does not spend one of the five daily publishes.'}, 'subject': {'type': 'string', 'description': 'WHAT THIS POST IS ABOUT — product, feature, offer or theme (e.g. "winter coat", "free trial"). post_performance\'s second grouping axis: reuse the exact wording, as with hook.'}, 'headings': {'enum': ['blocks', 'text'], 'type': 'string', 'description': 'how headings are rendered. “blocks” (default) uses X’s own heading block types for # and ## headings; ### and deeper become bold lines, because X Articles refuse a third-level heading (the reply says so). “text” renders every heading as a BOLD standalone paragraph instead: the words survive and the heading structure does not, and the reply says so.'}, 'allowLossy': {'type': 'boolean', 'description': 'publish even though part of the source cannot be represented on X, rendering those parts as plain text. OFF by default and it should usually stay off — silently publishing a user’s copy with formatting missing is worse than refusing and telling them.'}, 'coverImageUrl': {'type': 'string', 'description': 'optional cover picture for the Article — a Hermoso render URL or an upload_file url. Must be a STILL image; X Article covers are not videos.'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['prompt'], 'properties': {'cta': {'type': 'string', 'description': 'closing CTA line, ≤30 chars'}, 'specs': {'type': 'array', 'items': {'type': 'string'}, 'description': 'up to 4 spec lines for the typeset cards, ≤26 chars each'}, 'prompt': {'type': 'string', 'description': 'what the sizzle should show — the product, the setting, the look'}, 'seconds': {'type': 'number', 'description': 'finished length, clamped to 18-30s (default 25). The PAID hero render is always 15s regardless — this only changes how the cuts and cards are packed'}, 'refImage': {'type': 'string', 'description': 'product packshot URL that anchors the real label — strongly recommended'}, 'brandName': {'type': 'string', 'description': 'brand name on the cards — defaults to the workspace brand'}, 'musicMood': {'type': 'string', 'description': 'music-bed mood, e.g. driving / cinematic / upbeat'}, 'resolution': {'enum': ['480p', '720p', '1080p', '4k'], 'type': 'string', 'description': "hero-clip resolution and therefore the whole cost — DEFAULT '1080p' (≈1,040 credits); '720p' ≈470, '480p' ≈220, '4k' ≈4,130"}, 'aspectRatio': {'type': 'string', 'description': "'9:16' default; anything the seedance-2 catalog entry does not list falls back to 9:16"}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['productId', 'imageUrl'], 'properties': {'alt': {'type': 'string', 'description': 'alt text for accessibility and SEO; defaults to a generic credit'}, 'imageUrl': {'type': 'string', 'description': 'any public https image URL — a Hermoso render works, and so does ANY file of your own brought in with upload_file'}, 'productId': {'type': 'string', 'description': 'gid://shopify/Product/… from list_shopify_products'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'url': {'type': 'string', 'description': 'same as domain'}, 'name': {'type': 'string', 'description': 'same as companyName'}, 'sort': {'type': 'string', 'description': "'longest_running' (default) etc."}, 'brand': {'type': 'string', 'description': 'same as companyName'}, 'limit': {'type': 'number', 'description': 'max ads per platform (default 30)'}, 'domain': {'type': 'string', 'description': 'the advertiser domain, e.g. liquiddeath.com — a full website URL works (url / website are read as this too). Pass companyName OR domain'}, 'company': {'type': 'string', 'description': 'same as companyName'}, 'country': {'type': 'string', 'description': "2-letter, default 'US'"}, 'website': {'type': 'string', 'description': 'same as domain'}, 'companyName': {'type': 'string', 'description': 'the advertiser name, e.g. "Liquid Death" (company / brand / name are read as this too)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'docUrl': {'type': 'string', 'description': 'a Google Docs URL to read — the document id is extracted from it'}, 'documentId': {'type': 'string', 'description': 'the document id (from create_doc)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'range': {'type': 'string', 'description': 'A1 range, e.g. "A1:D50" (default A1:Z1000)'}, 'sheetUrl': {'type': 'string', 'description': 'a Google Sheets URL to read — the spreadsheet id is extracted from it'}, 'spreadsheetId': {'type': 'string', 'description': 'the spreadsheet id (from create_sheet)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['image', 'video'], 'properties': {'tier': {'enum': ['pro', 'standard'], 'type': 'string', 'description': "'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"}, 'image': {'type': 'string', 'description': 'the actor/character image URL (who should appear). 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.'}, 'video': {'type': 'string', 'description': 'the reference video whose motion to re-perform'}, 'prompt': {'type': 'string', 'description': 'optional scene/style guidance'}, 'orientation': {'enum': ['video', 'image'], 'type': 'string', 'description': "which aspect to keep: the video's (default) or the image's"}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['video', 'aspectRatio'], 'properties': {'video': {'type': 'string', 'description': 'the source video URL'}, 'aspectRatio': {'enum': ['9:16', '1:1', '16:9', '4:3', '3:4', '21:9', '9:21'], 'type': 'string', 'description': 'the target aspect ratio'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['text'], 'properties': {'text': {'type': 'string', 'description': 'the fact/preference, concise'}, 'category': {'type': 'string', 'description': 'short bucket: Brand, Audience, Taste, Do, Don’t, or Preference (default General)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['imageUrl'], 'properties': {'brandId': {'type': 'string', 'description': 'a brand id/name from list_brands to clone for; omit to use the active brand'}, 'imageUrl': {'type': 'string', 'description': 'the URL of the static ad image to clone. Using a real person’s face, likeness or voice confirms you are them or have their consent, take full, unlimited responsibility for its use and will cover any claim against Hermoso (hermoso.ai/terms). Paid plan only; recorded.'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['email'], 'properties': {'email': {'type': 'string', 'description': 'the member’s email'}, 'confirm': {'type': 'boolean', 'description': 'REQUIRED true'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['creative'], 'properties': {'board': {'type': 'boolean', 'description': 'paint the storyboard board first (default: on for UGC and cinematic spots, off for a product-only commercial); false = render from the text shot list'}, 'model': {'type': 'string', 'description': 'video model id from hermoso_capabilities (default: the plan’s pick). Naming one is a DELIBERATE pick — the server asks before ever swapping it (no silent fallback)'}, 'music': {'anyOf': [{'type': 'boolean'}, {'type': 'string'}], 'description': "music bed: OFF unless asked. true = the plan's own music line; or the bed in words ('lo-fi jazz, brushed drums'); false = none"}, 'dryRun': {'type': 'boolean', 'description': 'return the routing decision (single pass vs stitched acts, resolved model + act lengths) and the exact credits the real render reserves, WITHOUT submitting a render — free, nothing charged'}, 'lockup': {'type': 'boolean', 'description': 'brand wordmark + tagline composited over the closing seconds. DEFAULT FALSE — set true ONLY when the user asks for branding on the close'}, 'creator': {'type': 'string', 'description': 'CAST A SAVED CREATOR in this ad — their id from list_creators, or the name you know them by (“Sarah”), or a PRESET AI creator from list_creators presets by exact name or id (free, no generation). 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 — and because we already have their picture, the character portrait this pipeline would otherwise generate is skipped, so casting somebody costs LESS than not casting them. 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.'}, 'endCard': {'type': 'boolean', 'description': 'append the branded end card. DEFAULT FALSE on every recipe — set true ONLY when the user asks for an end card (a clone of a video that had none should not grow one)'}, 'captions': {'type': 'boolean', 'description': "burn the plan's per-scene on-screen words as caption pills. DEFAULT FALSE on every recipe — set true ONLY when the user asks for on-screen text or captions; no recipe turns them on by itself"}, 'creative': {'type': 'object', 'properties': {}, 'description': 'the FULL structured output of plan_ad (must contain video_storyboard)', 'additionalProperties': {}}, 'ttsVoice': {'type': 'string', 'description': 'voiceover voice name (e.g. Rachel / George) when the plan voices over'}, 'faceRoute': {'enum': ['face_lane'], 'type': 'string', 'description': 'ONLY after a render came back saying the video model\'s safety check flagged a person\'s face: \'face_lane\' is the user\'s choice "I own the rights to this face". Send the SAME request again with it and the same face is rendered on Seedance through the face library, at the normal price. Sending it is the user\'s confirmation that they have the rights to that face (paid plans, like every real face). Never set it on your own; the other choices that refusal names are a different model (`model`) or another creator (`creator`).'}, 'textStyle': {'anyOf': [{'type': 'string'}, {'type': 'object', 'properties': {'font': {'type': 'string', 'description': 'sans|serif|elegant|condensed|hand or any Google Fonts family'}, 'size': {'anyOf': [{'type': 'string'}, {'type': 'number'}], 'description': "s|m|l|xl, '120px', or a 0.015-0.15 frame fraction"}, 'tilt': {'type': 'number', 'description': 'degrees, ±45'}, 'color': {'type': 'string', 'description': 'any CSS colour'}, 'italic': {'type': 'boolean'}, 'preset': {'type': 'string'}, 'shadow': {'type': 'boolean'}, 'weight': {'type': 'number'}, 'outline': {'type': 'boolean'}, 'subFont': {'type': 'string'}, 'describe': {'type': 'string', 'description': 'the look in words'}, 'position': {'anyOf': [{'type': 'string'}, {'type': 'number'}], 'description': 'top|center|lower|bottom or 0.05-0.95 from the top'}, 'textCase': {'enum': ['as-is', 'upper', 'lower', 'title'], 'type': 'string'}, 'cardColor': {'type': 'string'}, 'subItalic': {'type': 'boolean'}, 'background': {'type': 'string', 'description': 'none|pill|a colour'}, 'outlineColor': {'type': 'string'}}}], 'description': 'THE LOOK of captions and the end card, only with captions or endCard and only when the user described one: the look in WORDS ("chunky yellow comic letters, purple outline"), a preset (editorial: big 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.'}, 'resolution': {'enum': ['480p', '720p', '1080p', '4k'], 'type': 'string', 'description': "'1080p' default (what we ship and bill for); '480p'/'720p' = cheaper draft passes, '4k' = premium final delivery (more credits). NOT EVERY MODEL OFFERS EVERY TIER — this enum is what the tool accepts, and each model's OWN `resolutions` list in hermoso_capabilities is what it can actually render (the longest-clip 30s model, for one, tops out at 720p). Ask for a tier the chosen model does not list and it is rendered at that model's best available tier instead, with nothing in the reply saying so — so check `resolutions` before promising anyone 1080p or 4k."}, 'aspectRatio': {'type': 'string', 'description': 'output aspect ratio, e.g. 9:16 (default) / 1:1 / 16:9'}, 'foundations': {'enum': ['choose'], 'type': 'string', 'description': "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"}, 'moodboardPick': {'type': 'integer', 'maximum': 4, 'minimum': 1, 'description': 'which moodboard panel (1-4, left to right, top to bottom) the storyboard follows; default 1'}, 'durationSeconds': {'type': 'number', 'description': 'total ad length in seconds (supported range 4–180; outside that it is clamped). Omit to honor the plan’s own duration — that is almost always right. This only RE-TIMES an already-authored board (its scenes are scaled to fit), it does NOT re-write it, so to change the length of the ad the user asked for, re-run plan_ad with durationSeconds instead. A length that fits ONE clip of the render model renders as one continuous pass; longer is stitched from acts filled to that model’s clip maximum with the remainder last — the maximum is 15s on most models and 30s on the longest-clip one, so use dryRun:true to see the exact act split for free before spending.'}, 'foundationImages': {'type': 'object', 'properties': {'hero': {'type': 'string'}, 'location': {'type': 'string'}, 'moodboard': {'type': 'string'}, 'identitySheet': {'type': 'string'}}, 'description': "images a foundations:'choose' call returned, reused as-is (not repainted)"}, 'allowGenericProduct': {'type': 'boolean', 'description': 'proceed even though this brand has NO product photo on file and the ad features a product — the packaging will be INVENTED. Only pass true after telling the user that and hearing they are fine with a generic stand-in'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['summary', 'details'], 'properties': {'details': {'type': 'string', 'description': 'what you were doing, the tool + arguments you called, what you expected, and what actually happened (paste the exact error)'}, 'summary': {'type': 'string', 'description': 'one-line summary of the bug'}, 'severity': {'enum': ['low', 'medium', 'high'], 'type': 'string', 'description': 'high = blocks the task or loses paid work; medium = wrong output but workable; low = cosmetic'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['summary', 'details'], 'properties': {'details': {'type': 'string', 'description': "what the user was actually trying to achieve, why the current tools couldn't do it, and what you'd expect the capability to do"}, 'summary': {'type': 'string', 'description': 'one line: the capability you need'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['id'], 'properties': {'at': {'type': 'string', 'description': 'the new time — ISO timestamp (2026-08-05T09:00:00Z) or epoch milliseconds. Must be in the future, at most 365 days out.'}, 'id': {'type': 'string', 'description': 'the scheduled post id from list_scheduled'}, 'link': {'type': 'string'}, 'poll': {'type': 'object', 'required': ['options'], 'properties': {'options': {'type': 'array', 'items': {'type': 'string'}}, 'durationMinutes': {'type': 'number'}}, 'description': 'X — replaces the poll; an empty options list removes it.'}, 'tags': {'type': 'array', 'items': {'type': 'string'}, 'description': 'YOUTUBE — replaces the WHOLE tag list; an empty array [] clears the tags.'}, 'brand': {'type': 'string', 'description': 'WHICH BRAND this post is in — id or exact name from list_brands; needed when it is not the brand this connection is pinned to. A name that matches no brand, or two, is REFUSED.'}, 'event': {'type': 'object', 'properties': {'title': {'type': 'string'}, 'endDate': {'type': 'string'}, 'endTime': {'type': 'string'}, 'startDate': {'type': 'string'}, 'startTime': {'type': 'string'}}, 'description': 'GOOGLE BUSINESS — replaces the whole event record {title, startDate, startTime, endDate, endTime}.'}, 'offer': {'type': 'object', 'properties': {'couponCode': {'type': 'string'}, 'redeemOnlineUrl': {'type': 'string'}, 'termsConditions': {'type': 'string'}}, 'description': 'GOOGLE BUSINESS — replaces the whole offer record {couponCode, redeemOnlineUrl, termsConditions}.'}, 'place': {'type': 'string', 'description': 'FACEBOOK — replaces the tagged place (the numeric id of its Facebook Page); an empty string removes it.'}, 'story': {'type': 'boolean', 'description': 'INSTAGRAM — true makes it a 24-hour Story, false an ordinary feed post. One image or one video, no carousel.'}, 'title': {'type': 'string', 'description': 'PINTEREST / YOUTUBE — replace the headline; "" clears it and goes back to deriving one from the caption'}, 'chatId': {'type': 'string', 'description': 'TELEGRAM — send it to a different chat, group or channel (@username or numeric id). It can be changed but never cleared: telegram cannot publish without one.'}, 'ideaId': {'type': 'string', 'description': 'short id of the content-plan idea this post came from'}, 'pageId': {'type': 'string', 'description': 'FACEBOOK / INSTAGRAM / THREADS — publish from a different connected Page (list_meta_pages)'}, 'recipe': {'type': 'string', 'description': 'the post\'s FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format'}, 'thread': {'type': 'array', 'items': {'type': 'string'}, 'description': 'X — replaces the WHOLE thread; an explicit [] drops back to a single post using the caption.'}, 'altText': {'anyOf': [{'type': 'string'}, {'type': 'array', 'items': {'type': 'string'}}], 'description': 'ACCESSIBILITY — replace the screen-reader description(s). A STRING describes every slide; an ARRAY describes them one at a time in slide order and REPLACES the whole list. "" clears it.'}, 'audioId': {'type': 'string', 'description': 'INSTAGRAM REEL — put one of Instagram’s OWN licensed music tracks under the Reel: the `id` search_instagram_audio returns. Instagram mixes it in when the Reel is published; nothing is downloaded or re-rendered. REELS ONLY; an empty string removes it, and only on an Instagram account connected through Meta (a Facebook Page with a linked Instagram), which is Instagram’s own rule — the Instagram connector cannot take it and is refused by name.'}, 'boardId': {'type': 'string', 'description': 'PINTEREST — move the Pin to a different board (list_pinterest_boards)'}, 'message': {'type': 'string', 'description': 'replace the caption used for every channel that has no override'}, 'audience': {'type': 'object', 'properties': {'cities': {'type': 'array', 'items': {'type': 'string'}}, 'minAge': {'type': 'number'}, 'regions': {'type': 'array', 'items': {'type': 'string'}}, 'countries': {'type': 'array', 'items': {'type': 'string'}}}, 'description': 'FACEBOOK — replaces who can see the Page post {countries, regions, cities, minAge}; {} removes the limit.'}, 'captions': {'type': 'object', 'description': 'replaces the WHOLE per-channel caption map — send every override you want to keep, not just the new one', 'propertyNames': {'type': 'string'}, 'additionalProperties': {'type': 'string'}}, 'channels': {'type': 'array', 'items': {'enum': ['facebook', 'instagram', 'threads', 'tiktok', 'youtube', 'linkedin', 'x', 'pinterest', 'google_business', 'bluesky', 'telegram'], 'type': 'string'}, 'description': 'replaces the channel list'}, 'coverUrl': {'type': 'string', 'description': 'INSTAGRAM REEL — replaces the cover image url; an empty string removes it.'}, 'imageUrl': {'type': 'string', 'description': 'swap the image; "" removes it'}, 'linkName': {'type': 'string', 'description': 'FACEBOOK — replaces the link preview headline; an empty string removes the override.'}, 'topicTag': {'type': 'string', 'description': 'THREADS ONLY — one topic tag, without the leading #.'}, 'videoUrl': {'type': 'string', 'description': 'swap the video; "" removes it'}, 'xArticle': {'type': 'object', 'properties': {'title': {'type': 'string'}, 'headings': {'enum': ['blocks', 'text'], 'type': 'string'}}, 'description': 'X: replaces the X Article (title, headings); {} makes it an ordinary X post again.'}, 'audioName': {'type': 'string', 'description': 'INSTAGRAM REEL — replaces the audio track name; an empty string removes it.'}, 'coverAtMs': {'type': 'number', 'description': 'THE VIDEO COVER on every channel, as one frame in milliseconds (see schedule_post).'}, 'imageUrls': {'type': 'array', 'items': {'type': 'string'}, 'description': 'replace the CAROUSEL slides, in order; an empty array [] drops the carousel and goes back to a single image. Omit to leave the slides exactly as they are. The edited item is re-checked against the same carousel rules the create passed, so adding a channel that cannot swipe is refused now rather than posting slide 1 later.'}, 'slideText': {'type': 'array', 'items': {'type': 'object', 'properties': {'link': {'type': 'string'}, 'title': {'type': 'string'}, 'description': {'type': 'string'}}}, 'description': 'PINTEREST CAROUSEL ONLY — per-slide title / description / link, aligned to slide order.'}, 'topicType': {'enum': ['STANDARD', 'EVENT', 'OFFER', 'ALERT'], 'type': 'string', 'description': 'GOOGLE BUSINESS — the kind of Post; EVENT and OFFER both require `event`.'}, 'trialReel': {'enum': ['MANUAL', 'SS_PERFORMANCE', ''], 'type': 'string', 'description': 'INSTAGRAM — replaces the trial-reel setting on a queued Reel (MANUAL or SS_PERFORMANCE); an explicit "" turns the trial off and it goes out as an ordinary Reel. Only takes effect while the post is still queued — a Reel already published cannot be converted into a trial.'}, 'yourBrand': {'type': 'boolean', 'description': 'TIKTOK — the own-brand disclosure; false turns it off.'}, 'actionType': {'enum': ['BOOK', 'ORDER', 'SHOP', 'LEARN_MORE', 'SIGN_UP', 'CALL'], 'type': 'string', 'description': 'GOOGLE BUSINESS — the call-to-action button; "" clears it.'}, 'locationId': {'type': 'string', 'description': 'GOOGLE BUSINESS — a different listing (list_business_locations)'}, 'madeWithAi': {'type': 'boolean', 'description': 'X — the AI-media label; false turns it off.'}, 'visibility': {'enum': ['public', 'unlisted', 'private', 'draft'], 'type': 'string', 'description': 'NOTE: changing this without also naming visibilityByChannel clears any per-channel overrides, so "make it all draft" is not a no-op'}, 'aiGenerated': {'type': 'boolean', 'description': 'INSTAGRAM / FACEBOOK REEL — Meta’s is_ai_generated self-disclosure. OMIT IT and Hermoso decides from provenance: a Hermoso render is declared, media that came through upload_file or from an external URL (the user’s own photographs or footage) is NOT — a real photo must never carry Instagram’s “AI info” label. Pass true or false only to override: false strips the label from something Hermoso would otherwise declare, true declares a render the user uploaded themselves.'}, 'audioVolume': {'type': 'integer', 'maximum': 100, 'minimum': 0, 'description': 'INSTAGRAM REEL — how loud that track plays, 0–100 (Instagram’s default 100). Needs audioId.'}, 'communityId': {'type': 'string', 'description': 'X — the community to publish into; an empty string goes back to the main timeline.'}, 'description': {'type': 'string', 'description': 'YOUTUBE — replace the video description; "" clears it and the caption is used.'}, 'disableDuet': {'type': 'boolean', 'description': 'TIKTOK VIDEO ONLY — block Duets.'}, 'linkPicture': {'type': 'string', 'description': 'FACEBOOK — replaces the link preview image url; an empty string removes the override.'}, 'quotePostId': {'type': 'string', 'description': 'THREADS ONLY — the id of the Threads post this one quotes.'}, 'shareToFeed': {'type': 'boolean', 'description': 'INSTAGRAM REEL — whether the Reel also shows in the Feed grid.'}, 'thumbOffset': {'type': 'number', 'description': 'INSTAGRAM REEL — replaces the cover frame, in milliseconds; 0 removes it. Never together with coverUrl.'}, 'videoVolume': {'type': 'integer', 'maximum': 100, 'minimum': 0, 'description': 'INSTAGRAM REEL — how loud the video’s own sound plays under the track, 0–100 (Instagram’s default 100; 0 = the track alone). Needs audioId.'}, 'callToAction': {'enum': ['BOOK_TRAVEL', 'BUY_NOW', 'CALL_NOW', 'DOWNLOAD', 'GET_DIRECTIONS', 'LEARN_MORE', 'LIKE_PAGE', 'MESSAGE_PAGE', 'NO_BUTTON', 'OPEN_LINK', 'SHOP_NOW', 'SIGN_UP', 'WATCH_MORE', ''], 'type': 'string', 'description': 'FACEBOOK — replaces the button on the Page post; "" removes it.'}, 'countryCodes': {'type': 'array', 'items': {'type': 'string'}, 'description': 'THREADS ONLY — two-letter country codes limiting who can see the post.'}, 'optimizeCopy': {'type': 'boolean', 'description': 'fit the shared caption to each channel’s own rules when you schedule it (the fitted caption is stored on the scheduled post, so what you scheduled is what publishes) wherever no per-channel caption was written; a channel with its own caption is left exactly as written. Send false to switch it off on this item.'}, 'privacyLevel': {'enum': ['PUBLIC_TO_EVERYONE', 'MUTUAL_FOLLOW_FRIENDS', 'FOLLOWER_OF_CREATOR', 'SELF_ONLY'], 'type': 'string', 'description': 'TIKTOK — WHICH PRIVACY LEVEL the post goes out at, in TikTok’s own vocabulary. TikTok requires the USER to choose this from the levels their own account allows: call tiktok_creator_info, show them the real options, and pass back the one they picked — never a default and never a guess, which TikTok refuses at init. Omit it and the post falls back to the coarse `visibility` (private -> SELF_ONLY, otherwise PUBLIC_TO_EVERYONE), which cannot express MUTUAL_FOLLOW_FRIENDS or FOLLOWER_OF_CREATOR at all. It must agree with the TikTok visibility (SELF_ONLY is the private one) and it is refused on a TikTok DRAFT, which carries no post info.'}, 'replyControl': {'enum': ['everyone', 'accounts_you_follow', 'mentioned_only', 'parent_post_author_only', 'followers_only'], 'type': 'string', 'description': 'THREADS ONLY — who may reply.'}, 'thumbnailUrl': {'type': 'string', 'description': 'YOUTUBE: replace the custom thumbnail; "" goes back to a frame of the video, "auto" to YouTube’s pick.'}, 'xQuotePostId': {'type': 'string', 'description': 'X — the post this one QUOTES; an empty string removes the quote. Named apart from the Threads `quotePostId` on this same schedule.'}, 'collaborators': {'type': 'array', 'items': {'type': 'string'}, 'description': 'INSTAGRAM — replaces the WHOLE collab list (up to 3 usernames); an explicit [] removes the co-authors and the post goes out as an ordinary single-author post. Only takes effect if the post has not fired yet — an invite already sent cannot be withdrawn from here.'}, 'coverImageUrl': {'type': 'string', 'description': 'THE VIDEO COVER on every channel, as a Hermoso-hosted picture (see schedule_post).'}, 'disableStitch': {'type': 'boolean', 'description': 'TIKTOK VIDEO ONLY — block Stitches.'}, 'platformCover': {'type': 'boolean', 'description': 'VIDEO COVER. Omit it (the default) and Hermoso sets the video’s best frame — the same frame as its Library thumbnail — as the cover on every channel that allows one — and on X, Threads and Bluesky, which have none, a blank first frame is replaced on a copy sent there only. true = send no cover and let the platform pick (usually the first frame). A cover you pass yourself always wins.'}, 'replySettings': {'enum': ['following', 'mentionedUsers', 'subscribers', 'verified'], 'type': 'string', 'description': 'X — who may reply; "" goes back to everyone.'}, 'brandedContent': {'type': 'boolean', 'description': 'TIKTOK — the paid-partnership disclosure; false turns it off.'}, 'disableComment': {'type': 'boolean', 'description': 'TIKTOK — comments off on this post.'}, 'linkAttachment': {'type': 'string', 'description': 'THREADS ONLY — a full http(s) URL rendered as a link card on a TEXT-ONLY post. The only way a Threads post carries a destination.'}, 'targetAudience': {'type': 'object', 'properties': {'degrees': {'type': 'array', 'items': {'type': 'string'}}, 'industries': {'type': 'array', 'items': {'type': 'string'}}, 'seniorities': {'type': 'array', 'items': {'type': 'string'}}, 'geoLocations': {'type': 'array', 'items': {'type': 'string'}}, 'jobFunctions': {'type': 'array', 'items': {'type': 'string'}}, 'fieldsOfStudy': {'type': 'array', 'items': {'type': 'string'}}, 'organizations': {'type': 'array', 'items': {'type': 'string'}}, 'staffCountRanges': {'type': 'array', 'items': {'type': 'string'}}}, 'description': 'LINKEDIN COMPANY PAGE — replaces who sees the post; {} removes the limit. The matching audience must be over 300 followers.'}, 'linkDescription': {'type': 'string', 'description': 'FACEBOOK — replaces the link preview description; an empty string removes the override.'}, 'paidPartnership': {'type': 'boolean', 'description': 'INSTAGRAM AND X — the paid-partnership label; false turns it off.'}, 'callToActionLink': {'type': 'string', 'description': 'FACEBOOK — replaces where the button goes; an empty string falls back to the post link.'}, 'coverTimestampMs': {'type': 'number', 'description': 'TIKTOK VIDEO ONLY — cover frame in milliseconds.'}, 'crossreshareToIg': {'type': 'boolean', 'description': 'THREADS ONLY — also share to Instagram as a Story when it fires; false turns it off. Refused on a carousel.'}, 'commercialContent': {'type': 'boolean', 'description': 'TIKTOK — the COMMERCIAL CONTENT DISCLOSURE toggle: true when this post promotes a brand, product or service at all. TikTok requires at least one of yourBrand / brandedContent once it is on, and refuses a post that declares itself commercial without naming which kind. Either disclosure already implies it.'}, 'instagramLocationId': {'type': 'string', 'description': 'INSTAGRAM — replaces the tagged place (the numeric id of its Facebook Page); an empty string removes it. Not locationId, which is the Google Business listing.'}, 'visibilityByChannel': {'type': 'object', 'propertyNames': {'type': 'string'}, 'additionalProperties': {'type': 'string'}}, 'crossreshareDarkMode': {'type': 'boolean', 'description': 'THREADS ONLY — dark-mode that Instagram Story. Needs crossreshareToIg.'}, 'linkedinOrganizationId': {'type': 'string', 'description': 'LINKEDIN — target a different company Page, or "" to post as the connected person instead'}, 'brandedContentSponsorIds': {'type': 'array', 'items': {'type': 'string'}, 'description': 'INSTAGRAM — replaces the sponsor user ids behind the paid-partnership label (at most 2); [] removes them.'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['query'], 'properties': {'brand': {'anyOf': [{'type': 'string'}, {'type': 'object', 'properties': {}, 'additionalProperties': {}}], 'description': 'brand name or profile object to tailor the research to; omit to use the workspace’s saved brand'}, 'query': {'type': 'string', 'description': 'what to research, e.g. "the longest-running protein-pancake ads on Meta"'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['image'], 'properties': {'image': {'type': 'string', 'description': 'the finished static ad: URL, Library item URL, upload_file URL or local path'}, 'fixLabel': {'type': 'boolean', 'description': 'false = leave the product label exactly as rendered: no product lookup, no label check, no re-print (default on when the ad shows the brand’s saved product)'}, 'aspectRatios': {'type': 'array', 'items': {'enum': ['1:1', '4:5', '9:16', '16:9', '3:4', '4:3'], 'type': 'string'}, 'description': 'target canvases (default 1:1, 4:5, 9:16)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['id'], 'properties': {'at': {'type': 'string', 'description': 'hold the retry until a later time — ISO timestamp or epoch milliseconds. Leave it out to retry immediately, which is almost always what you want. A time you name here must be at least a minute from now, exactly like any other scheduled post.'}, 'id': {'type': 'string', 'description': 'the scheduled post id from list_scheduled'}, 'brand': {'type': 'string', 'description': 'WHICH BRAND this post is in — id or exact name from list_brands; needed when it is not the brand this connection is pinned to. A name that matches no brand, or two, is REFUSED.'}, 'chatId': {'type': 'string', 'description': 'CORRECT THE TELEGRAM DESTINATION on retry — the @username or numeric id of the chat. A post aimed at a chat the bot is not in fails every time it is retried until this changes.'}, 'pageId': {'type': 'string', 'description': 'CORRECT THE PAGE on retry — which connected Facebook Page (and its linked Instagram/Threads) publishes, from list_meta_pages.'}, 'boardId': {'type': 'string', 'description': 'CORRECT THE BOARD on retry — the Pinterest board the Pin goes on, from list_pinterest_boards. A Pin aimed at a board Pinterest refuses fails the same way on every retry until this is changed.'}, 'message': {'type': 'string', 'description': 'CORRECT THE CAPTION on retry — use this when the original was refused for length or content. Anything not named here is copied from the original post.'}, 'captions': {'type': 'object', 'description': 'CORRECT ONE CHANNEL’S CAPTION on retry, e.g. { "x": "..." } when only that channel refused the text.', 'propertyNames': {'type': 'string'}, 'additionalProperties': {'type': 'string'}}, 'channels': {'type': 'array', 'items': {'enum': ['facebook', 'instagram', 'threads', 'tiktok', 'youtube', 'linkedin', 'x', 'pinterest', 'google_business', 'bluesky', 'telegram'], 'type': 'string'}, 'description': 'retry only these channels (default: every channel that did not publish)'}, 'locationId': {'type': 'string', 'description': 'CORRECT THE LISTING on retry — which Google Business Profile location, e.g. "locations/123" from list_business_locations.'}, 'allowDuplicate': {'type': 'boolean', 'description': 'ONLY for a channel you have checked by hand and confirmed the post is genuinely NOT there. It bypasses the double-post protection and can publish a second public copy, so never set it to work around a refusal you have not investigated.'}, 'linkedinOrganizationId': {'type': 'string', 'description': 'CORRECT THE LINKEDIN AUTHOR on retry — the company Page id from list_linkedin_pages. Set it to an empty string to fall back to the personal profile.'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'force': {'type': 'boolean', 'description': 'plan even while the refill is switched off — useful for showing someone what it would do before they turn it on. Combined with dryRun:false it still respects a stored dryRun.'}, 'dryRun': {'type': 'boolean', 'description': 'default TRUE (preview only). false actually queues the posts.'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['name'], 'properties': {'look': {'type': 'string', 'description': 'their canonical wardrobe/appearance in words — reused to hold the look steady across ads'}, 'name': {'type': 'string', 'description': 'what to call this creator (e.g. “Sarah”) — list_creators and the app’s picker match on it'}, 'image': {'type': 'string', 'description': 'REQUIRED except with useAnyway. public https url of the portrait (an existing render’s url, or any public photo). Not a local file path — upload it with upload_file first and save the url that returns'}, 'poses': {'type': 'array', 'items': {'type': 'string'}, 'description': 'up to 4 extra full-body / angle plates of the SAME person (public urls) — they make a wider shot hold the identity'}, 'voice': {'type': 'string', 'description': 'a default voice name for this persona (engines + voices are in hermoso_capabilities)'}, 'source': {'enum': ['generated', 'upload', 'social'], 'type': 'string', 'description': '"generated" (default) = an AI-made person; "upload" / "social" = a REAL person'}, 'useAnyway': {'type': 'boolean', 'description': 'only for a creator whose saved photo was flagged too unclear to cast (render_ad says so): true casts the current photo as it is, no new image needed'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['name'], 'properties': {'name': {'type': 'string', 'description': 'the playbook headline — what it is, in a few words'}, 'brand': {'type': 'string', 'description': 'which brand this is for (defaults to the workspace brand)'}, 'hooks': {'type': 'array', 'items': {'type': 'string'}, 'description': 'the opening hooks worth reusing, verbatim'}, 'plays': {'type': 'array', 'items': {'type': 'object', 'required': ['title'], 'properties': {'title': {'type': 'string'}, 'detail': {'type': 'string'}}, 'additionalProperties': {}}, 'description': 'the concrete plays to run ({title, detail}) — the actionable half'}, 'angles': {'type': 'array', 'items': {'type': 'object', 'required': ['title'], 'properties': {'title': {'type': 'string'}, 'detail': {'type': 'string'}}, 'additionalProperties': {}}, 'description': 'the persuasion angles ({title, detail})'}, 'source': {'type': 'string', 'description': 'where it came from, e.g. “teardown · Ridge”'}, 'formats': {'type': 'array', 'items': {'type': 'string'}, 'description': 'the formats/recipes this plays best in (e.g. ugc_selfie, cinematic, static)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['name', 'directive'], 'properties': {'name': {'type': 'string', 'description': 'short skill name, e.g. “Founder-story hook”'}, 'directive': {'type': 'string', 'description': 'the full instruction the skill applies when used (1–6 sentences, imperative)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'url': {'type': 'string', 'description': 'a single Hermoso render URL to save'}, 'name': {'type': 'string', 'description': 'file name (single save)'}, 'urls': {'type': 'array', 'items': {'type': 'string'}, 'description': 'several render URLs (up to 20) to save in one call'}, 'folder': {'type': 'string', 'description': 'Drive folder name to save into (created if new)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'url': {'type': 'string', 'description': 'a single Hermoso render URL to save'}, 'name': {'type': 'string', 'description': 'file name (single save)'}, 'urls': {'type': 'array', 'items': {'type': 'string'}, 'description': 'several render URLs (up to 20) to save in one call'}, 'folder': {'type': 'string', 'description': 'OneDrive folder name to save into (created if new)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['collection', 'items'], 'properties': {'items': {'type': 'array', 'items': {'type': 'object', 'properties': {'key': {'type': 'string', 'description': 'a stable id for this ad if you have one (an ad_archive_id, creativeId, …). Omit and one is derived from the link/media so re-saving is idempotent'}, 'body': {'type': 'string', 'description': 'the ad copy'}, 'link': {'type': 'string', 'description': 'link to the ad in its library / the destination URL'}, 'image': {'type': 'string', 'description': 'image URL'}, 'title': {'type': 'string', 'description': 'headline / hook'}, 'video': {'type': 'string', 'description': 'video URL'}, 'pageName': {'type': 'string', 'description': 'alias of advertiser'}, 'platform': {'type': 'string', 'description': "where it ran — 'meta', 'google', 'linkedin', 'tiktok', 'generated', …"}, 'page_name': {'type': 'string', 'description': 'alias of advertiser: the field search_meta_ads returns, accepted as-is'}, 'advertiser': {'type': 'string', 'description': 'the brand running the ad'}}, 'additionalProperties': {}}, 'minItems': 1, 'description': 'the ads to save'}, 'collection': {'type': 'string', 'description': 'the collection name — an existing one, or a new one to create'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['channels'], 'properties': {'at': {'type': 'string', 'description': 'when to post — ISO timestamp (2026-08-01T09:00:00Z) or epoch milliseconds. Must be in the future, at most 365 days out. Give this OR useQueue, never both.'}, 'hook': {'type': 'string', 'description': 'WHAT ANGLE this post is built on, recorded only at publish time. post_performance ranks hooks on it (a winner needs 5 posts sharing ONE hook), so pass a list_hooks id (e.g. "direct_callout", "before_after") or reuse your own wording EXACTLY across a campaign. Omit it and this post never votes on which hook works.'}, 'link': {'type': 'string', 'description': 'a link to attach (Facebook)'}, 'poll': {'type': 'object', 'required': ['options'], 'properties': {'options': {'type': 'array', 'items': {'type': 'string'}}, 'durationMinutes': {'type': 'number'}}, 'description': 'X — attach a poll: {options:["…","…"], durationMinutes}. 2–4 options of at most 25 characters each; voting runs 5–10080 minutes (7 days), default 1440. X makes a poll MUTUALLY EXCLUSIVE with media, so an item carrying an image or video is refused — schedule the poll as its own X-only post.'}, 'tags': {'type': 'array', 'items': {'type': 'string'}, 'description': 'YOUTUBE — up to 30 search tags for the video (plain words, no #).'}, 'brand': {'type': 'string', 'description': "WHICH BRAND this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no brand, or two, is REFUSED and nothing is posted."}, 'event': {'type': 'object', 'properties': {'title': {'type': 'string'}, 'endDate': {'type': 'string'}, 'endTime': {'type': 'string'}, 'startDate': {'type': 'string'}, 'startTime': {'type': 'string'}}, 'description': 'GOOGLE BUSINESS — required for an EVENT or OFFER post: {title, startDate:"YYYY-MM-DD", endDate, startTime:"HH:MM", endTime}. `title` is the EVENT’s headline, a different thing from the post `title` (which is the Pinterest/YouTube one). Google documents its TimeInterval as needing all four date/time parts to be valid, so send the times whenever you know them.'}, 'offer': {'type': 'object', 'properties': {'couponCode': {'type': 'string'}, 'redeemOnlineUrl': {'type': 'string'}, 'termsConditions': {'type': 'string'}}, 'description': 'GOOGLE BUSINESS — OFFER posts only: {couponCode, redeemOnlineUrl, termsConditions}. redeemOnlineUrl is where an offer actually sends people, since the button link is ignored on an Offer.'}, 'place': {'type': 'string', 'description': 'FACEBOOK — tag a location on the Page post. The NUMERIC ID OF THE FACEBOOK PAGE for that place, not a place name and not coordinates.'}, 'story': {'type': 'boolean', 'description': 'INSTAGRAM STORY — publish this as a 24-hour Story instead of a feed post. One image OR one video: Instagram has no carousel story, so a carousel is REFUSED BY NAME rather than quietly posted to the feed. A Story carries NO CAPTION (there is nowhere to show one), no collaborators, no product tags and no trialReel — passing any of those is refused by name and nothing is posted, because a story that silently drops the words is worse than one that never went. INSTAGRAM ONLY: a Facebook Page story is a different upload and is not built, so a Facebook channel with `story` set is refused rather than published to the feed.'}, 'title': {'type': 'string', 'description': 'PINTEREST / YOUTUBE — the headline, max 100 characters. Pinterest shows it in search and under the pin; YouTube requires one. Leave it out and Hermoso derives one from that channel’s caption (first sentence, cut on a word boundary, trailing hashtags dropped) — set a real one whenever the caption does not open with a usable headline.'}, 'chatId': {'type': 'string', 'description': 'TELEGRAM — REQUIRED whenever telegram is a channel: WHICH chat, group or channel the bot posts to. A public channel’s @username (@hermosoai) or the numeric id (a group is negative; a supergroup or channel starts with -100). There is no default and there cannot be one — the Telegram Bot API publishes no method that lists a bot’s chats — so scheduling telegram without one is refused up front. list_telegram_chats finds ids for chats that have messaged the bot in the last 24 hours.'}, 'ideaId': {'type': 'string', 'description': 'short id of the content-plan idea this post came from'}, 'pageId': {'type': 'string', 'description': 'FACEBOOK / INSTAGRAM / THREADS — which connected Facebook Page (and its linked Instagram) publishes, from list_meta_pages. Needed when the brand has more than one Page connected; with several and no id the post is refused at fire time rather than sent from the wrong brand.'}, 'recipe': {'type': 'string', 'description': 'the post\'s FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format'}, 'thread': {'type': 'array', 'items': {'type': 'string'}, 'description': 'X — publish a THREAD, one entry per post, each replying to the one before (at most 25). 280 characters per part without X Premium, up to 25,000 with it; nothing is truncated. It REPLACES the X caption: with a thread set, `message`/`captions.x` is not sent to X at all. A thread cannot carry a poll.'}, 'altText': {'anyOf': [{'type': 'string'}, {'type': 'array', 'items': {'type': 'string'}}], 'description': 'ACCESSIBILITY — the screen-reader description of the attached image. ONE STRING describes the picture; on a CAROUSEL it describes EVERY slide. Pass an ARRAY of strings instead to describe each slide separately, aligned to the slide order — that is strictly better on a multi-slide post, because one sentence read out over six different pictures is wrong for five of them. More descriptions than pictures is refused rather than dropped. Write one whenever the post carries an image: describe what is IN the picture, never a repeat of the caption, which a screen reader already reads. CARRIED BY: X (max 1000, one per media), Pinterest (max 500 — PIN-LEVEL only, since its API has no per-item alt text, so slide 1’s description is used for the whole Pin and the result says the others were not sent), LinkedIn COMPANY PAGES (max 4086, one per slide), INSTAGRAM image posts and image slides (max 1000), FACEBOOK photos and albums, and BLUESKY, whose lexicon makes it REQUIRED on every image. The schedule is REFUSED if the LONGEST description exceeds the tightest of the channels on it, rather than truncated on the way out. NOT CARRIED, and none of these is a refusal — the post still publishes, just undescribed there, and the per-channel result says which: TikTok (its photo post has no alt field at any level), a THREADS CAROUSEL, an INSTAGRAM Reel or video slide, and a LinkedIn PERSONAL-profile post.'}, 'audioId': {'type': 'string', 'description': 'INSTAGRAM REEL — put one of Instagram’s OWN licensed music tracks under the Reel: the `id` search_instagram_audio returns. Instagram mixes it in when the Reel is published; nothing is downloaded or re-rendered. REELS ONLY, and only on an Instagram account connected through Meta (a Facebook Page with a linked Instagram), which is Instagram’s own rule — the Instagram connector cannot take it and is refused by name.'}, 'boardId': {'type': 'string', 'description': 'PINTEREST — REQUIRED whenever pinterest is a channel: the board the Pin goes on, from list_pinterest_boards. The user picks it; a Pin on the wrong board is a public mistake. Scheduling pinterest without one is refused immediately.'}, 'message': {'type': 'string', 'description': 'the caption/text used for every channel unless overridden in captions'}, 'subject': {'type': 'string', 'description': 'WHAT THIS POST IS ABOUT — product, feature, offer or theme (e.g. "winter coat", "free trial"). post_performance\'s second grouping axis: reuse the exact wording, as with hook.'}, 'accounts': {'type': 'object', 'description': 'WHICH accounts of a multi-account channel to post to, e.g. { "tiktok": ["@a", "@b"] } or { "tiktok": "all" } — one row per account at fire time, each with its own result. Omit for channels with one account (several and none named is refused by name).', 'propertyNames': {'type': 'string'}, 'additionalProperties': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}}, {'type': 'string', 'const': 'all'}]}}, 'audience': {'type': 'object', 'properties': {'cities': {'type': 'array', 'items': {'type': 'string'}, 'description': 'Meta location keys for cities'}, 'minAge': {'anyOf': [{'type': 'number', 'const': 13}, {'type': 'number', 'const': 15}, {'type': 'number', 'const': 18}, {'type': 'number', 'const': 21}, {'type': 'number', 'const': 25}]}, 'regions': {'type': 'array', 'items': {'type': 'string'}, 'description': 'Meta location keys for regions/states'}, 'countries': {'type': 'array', 'items': {'type': 'string'}, 'description': 'two-letter codes, e.g. ["CA","US"]'}}, 'description': 'FACEBOOK PAGE POST ONLY — limit who can see this organic post to people in these places and/or over this age (Facebook allows 13, 15, 18, 21 or 25). Not on Instagram, Threads or a Facebook Reel (a vertical clip becomes a Reel): those are refused. Region and city keys come from the Meta ads location search.'}, 'captions': {'type': 'object', 'description': 'per-channel caption overrides, e.g. { "instagram": "…", "threads": "…" } — platforms want different lengths and hashtag conventions', 'propertyNames': {'type': 'string'}, 'additionalProperties': {'type': 'string'}}, 'channels': {'type': 'array', 'items': {'enum': ['facebook', 'instagram', 'threads', 'tiktok', 'youtube', 'linkedin', 'x', 'pinterest', 'google_business', 'bluesky', 'telegram'], 'type': 'string'}, 'description': 'one or more channels to post to at that time'}, 'coverUrl': {'type': 'string', 'description': 'INSTAGRAM REEL COVER — a public image url Instagram fetches and uses as the cover in the Reels tab. REELS ONLY, and the alternative to `thumbOffset`: passing both is refused, since they are two answers to the same question. Run a local file through upload_file first.'}, 'imageUrl': {'type': 'string', 'description': 'a Hermoso render URL (a /generated path, or what upload_file returned), a data: URI, or a public https URL. NOTE: only Facebook/Instagram/Threads accept an arbitrary public URL — X, TikTok, YouTube, LinkedIn, Pinterest and Google Business re-host the bytes and REFUSE anything that is not a Hermoso render, so run an external file through upload_file first and schedule that url.'}, 'linkName': {'type': 'string', 'description': 'FACEBOOK — override the HEADLINE of the link preview. Without it the post shows whatever Open Graph title the destination carries, which on a bare landing page is often nothing. Needs `link`. MEASURED 2026-09-17: Facebook only honours these when the Page’s business has VERIFIED the domain (Business Manager > Brand safety > Domains) — without that Meta refuses the post outright with “Only owners of the URL…”, so leave them out unless the brand owns and has verified that link’s domain.'}, 'timezone': {'type': 'string', 'description': 'IANA zone for the queue, e.g. "America/New_York" — only meaningful with useQueue, and it overrides the brand’s saved zone for this one post. A saved slot of "09:00" is a WALL-CLOCK time, so the zone is what turns it into an instant; without either the brand’s saved zone or this, the queue resolves in UTC.'}, 'topicTag': {'type': 'string', 'description': 'THREADS ONLY — one topic tag for the post, without the leading #.'}, 'useQueue': {'type': 'boolean', 'description': 'instead of naming a minute, drop this into the brand’s POSTING QUEUE: Hermoso takes the earliest of its saved posting times that is still free (skipping any slot another queued post already holds). This is the natural answer to “just queue it” / “post it at my next opening”. Mutually exclusive with `at` — passing both is refused rather than one being silently preferred. If the brand has no posting times set, or every slot for the next 90 days is taken, it is refused by name and nothing is scheduled.'}, 'videoUrl': {'type': 'string', 'description': 'a Hermoso render URL (a /generated path, or what upload_file returned), a data: URI, or a public https URL — required for youtube, and for tiktok unless you pass an imageUrl (TikTok takes a photo post too). Same origin rule as imageUrl: everything except Facebook/Instagram/Threads REFUSES a non-Hermoso URL, so pass external video through upload_file first.'}, 'xArticle': {'type': 'object', 'required': ['title'], 'properties': {'title': {'type': 'string'}, 'headings': {'enum': ['blocks', 'text'], 'type': 'string'}}, 'description': 'X: publish the X item as a long-form X ARTICLE. title is its headline, the X text (message or captions.x) its markdown body, the image its cover. Refused now if the markdown has formatting X cannot hold. X allows about 5 Articles a day; one that fires into that cap fails with the reset time and can be retried.'}, 'audioName': {'type': 'string', 'description': 'INSTAGRAM REEL — the name of the Reel’s audio track, which is what viewers tap through to. REELS ONLY.'}, 'coverAtMs': {'type': 'number', 'description': 'THE VIDEO COVER on EVERY channel of this post, as ONE frame: milliseconds from the start (7000 = the frame at 7s). Instagram, TikTok, Facebook, LinkedIn Page, Pinterest, Telegram and YouTube all get that frame; X, Threads and Bluesky have no cover setting. A channel’s own field (thumbOffset, coverTimestampMs, coverUrl) wins there and otherwise counts as this.'}, 'imageUrls': {'type': 'array', 'items': {'type': 'string'}, 'description': 'CAROUSEL — an ORDERED list of image URLs to publish as ONE swipeable post on every channel that supports it (Instagram 2–10, Threads 2–20, Facebook, LinkedIn company Pages 2–20, Pinterest 2–5, TikTok up to 35 as a photo post). Use this whenever the creative is a multi-slide deck: a scheduled post carrying only slide 1 of a “1/6 · SWIPE” set is a broken ad that nobody is watching when it fires. THE ORDER IS THE PRODUCT. A channel on this schedule that cannot do carousels — X, YouTube, Google Business Profile — is REFUSED NOW, with the reason, so you can drop it or give it its own single image; it is never quietly downgraded hours later.'}, 'slideText': {'type': 'array', 'items': {'type': 'object', 'properties': {'link': {'type': 'string'}, 'title': {'type': 'string'}, 'description': {'type': 'string'}}}, 'description': 'PINTEREST CAROUSEL ONLY — per-slide title / description / link, aligned to slide order. Every other platform takes ONE caption for the whole carousel.'}, 'topicType': {'enum': ['STANDARD', 'EVENT', 'OFFER', 'ALERT'], 'type': 'string', 'description': 'GOOGLE BUSINESS — the KIND of Post. STANDARD is the default; EVENT and OFFER both REQUIRE `event` (title + start date), and OFFER also takes `offer`.'}, 'trialReel': {'enum': ['MANUAL', 'SS_PERFORMANCE'], 'type': 'string', 'description': 'INSTAGRAM TRIAL REEL — publish this Reel to NON-FOLLOWERS ONLY at first (Instagram allows trials only on accounts above its follower threshold — about 1,000 followers; an ineligible account is refused by name and nothing is posted), so a hook can be tested on a cold audience without spending it on the people who already follow the brand; Instagram shows it to followers only if it graduates. MANUAL = the creator graduates it by hand in the Instagram app; SS_PERFORMANCE = Instagram graduates it automatically if it performs. REELS ONLY and INSTAGRAM ONLY: an image, a carousel, or a Facebook/Threads channel is REFUSED BY NAME rather than quietly published as an ordinary post — a trial that silently goes to every follower is the exact opposite of what was asked for, so Instagram must be one of the `channels` and the item must carry a video. Omit it for a normal Reel.'}, 'yourBrand': {'type': 'boolean', 'description': 'TIKTOK — the OWN-BRAND disclosure (brand_organic_toggle): true when the post promotes the creator’s own business. TikTok asks for at least one of this and brandedContent once a post is commercial.'}, 'actionType': {'enum': ['BOOK', 'ORDER', 'SHOP', 'LEARN_MORE', 'SIGN_UP', 'CALL'], 'type': 'string', 'description': 'GOOGLE BUSINESS — the call-to-action button. Every button except CALL needs `link` (CALL dials the number on the listing and takes none). Google IGNORES the button link on an OFFER post — put the destination in offer.redeemOnlineUrl. Omit and a post carrying a link gets LEARN_MORE.'}, 'locationId': {'type': 'string', 'description': "GOOGLE BUSINESS PROFILE — which listing, e.g. 'locations/123' from list_business_locations. Needed when the account manages more than one storefront; it is never chosen for the user."}, 'madeWithAi': {'type': 'boolean', 'description': 'X — X’s AI-media label on this post. Opt-in: X treats it as the poster’s own claim about their media, so it is never set on the user’s behalf.'}, 'visibility': {'enum': ['public', 'unlisted', 'private', 'draft'], 'type': 'string', 'description': "how it should be published — DEFAULT 'public' (live). Only pass something else if the user explicitly asked to stage/hide it. Not every channel supports every value; an impossible combination is refused when you schedule it, with the reason."}, 'aiGenerated': {'type': 'boolean', 'description': 'INSTAGRAM / FACEBOOK REEL — Meta’s is_ai_generated self-disclosure. OMIT IT and Hermoso decides from provenance: a Hermoso render is declared, media that came through upload_file or from an external URL (the user’s own photographs or footage) is NOT — a real photo must never carry Instagram’s “AI info” label. Pass true or false only to override: false strips the label from something Hermoso would otherwise declare, true declares a render the user uploaded themselves.'}, 'audioVolume': {'type': 'integer', 'maximum': 100, 'minimum': 0, 'description': 'INSTAGRAM REEL — how loud that track plays, 0–100 (Instagram’s default 100). Needs audioId.'}, 'communityId': {'type': 'string', 'description': 'X — publish into an X COMMUNITY instead of the main timeline: the number in the community’s own URL (x.com/i/communities/<id>). The connected account must be a MEMBER of it.'}, 'description': {'type': 'string', 'description': 'YOUTUBE: the video DESCRIPTION, max 5000 characters, carrying the links, the CTA and what YouTube search reads. Omit it and the caption is used.'}, 'disableDuet': {'type': 'boolean', 'description': 'TIKTOK VIDEO ONLY — block Duets. TikTok’s photo-post API has no Duets, so this is refused on a photo/slideshow item rather than silently dropped.'}, 'linkPicture': {'type': 'string', 'description': 'FACEBOOK — override the IMAGE of the link preview: a public http(s) url Facebook fetches itself. Needs `link`. MEASURED 2026-09-17: Facebook only honours these when the Page’s business has VERIFIED the domain (Business Manager > Brand safety > Domains) — without that Meta refuses the post outright with “Only owners of the URL…”, so leave them out unless the brand owns and has verified that link’s domain.'}, 'quotePostId': {'type': 'string', 'description': 'THREADS ONLY — the id of the Threads post this one quotes.'}, 'shareToFeed': {'type': 'boolean', 'description': 'INSTAGRAM REEL — true puts the Reel in the Feed grid as well as the Reels tab. REELS ONLY. Left unset it follows Instagram’s own default; Hermoso does not flip it either way on the user’s behalf.'}, 'thumbOffset': {'type': 'number', 'description': 'INSTAGRAM REEL COVER, the other way — which frame becomes the cover, in MILLISECONDS from the start of the video. REELS ONLY. Use it instead of `coverUrl` when the right cover is already a frame of the clip.'}, 'videoVolume': {'type': 'integer', 'maximum': 100, 'minimum': 0, 'description': 'INSTAGRAM REEL — how loud the video’s own sound plays under the track, 0–100 (Instagram’s default 100; 0 = the track alone). Needs audioId.'}, 'callToAction': {'enum': ['BOOK_TRAVEL', 'BUY_NOW', 'CALL_NOW', 'DOWNLOAD', 'GET_DIRECTIONS', 'LEARN_MORE', 'LIKE_PAGE', 'MESSAGE_PAGE', 'NO_BUTTON', 'OPEN_LINK', 'SHOP_NOW', 'SIGN_UP', 'WATCH_MORE'], 'type': 'string', 'description': 'FACEBOOK — a real call-to-action BUTTON on the Page post. The types that open a destination (SHOP_NOW, LEARN_MORE, SIGN_UP, BUY_NOW, DOWNLOAD, OPEN_LINK, WATCH_MORE, BOOK_TRAVEL) need somewhere to go: the post’s own `link`, or `callToActionLink`. CALL_NOW, MESSAGE_PAGE, LIKE_PAGE and GET_DIRECTIONS act on the Page itself and take none.'}, 'countryCodes': {'type': 'array', 'items': {'type': 'string'}, 'description': 'THREADS ONLY — two-letter country codes to limit who can see the post. Omit to show it everywhere.'}, 'optimizeCopy': {'type': 'boolean', 'description': 'RECOMMENDED when one caption goes to several channels: fit the shared caption to each channel’s own rules when you schedule it (the fitted caption is stored on the scheduled post, so what you scheduled is what publishes) wherever no per-channel caption was written. The angle and every claim stay the author’s; a channel with its own caption is left exactly as written. Off by default so nobody’s words are rewritten unasked.'}, 'privacyLevel': {'enum': ['PUBLIC_TO_EVERYONE', 'MUTUAL_FOLLOW_FRIENDS', 'FOLLOWER_OF_CREATOR', 'SELF_ONLY'], 'type': 'string', 'description': 'TIKTOK — WHICH PRIVACY LEVEL the post goes out at, in TikTok’s own vocabulary. TikTok requires the USER to choose this from the levels their own account allows: call tiktok_creator_info, show them the real options, and pass back the one they picked — never a default and never a guess, which TikTok refuses at init. Omit it and the post falls back to the coarse `visibility` (private -> SELF_ONLY, otherwise PUBLIC_TO_EVERYONE), which cannot express MUTUAL_FOLLOW_FRIENDS or FOLLOWER_OF_CREATOR at all. It must agree with the TikTok visibility (SELF_ONLY is the private one) and it is refused on a TikTok DRAFT, which carries no post info.'}, 'replyControl': {'enum': ['everyone', 'accounts_you_follow', 'mentioned_only', 'parent_post_author_only', 'followers_only'], 'type': 'string', 'description': "THREADS ONLY — who may reply. Omit for Threads' own default (everyone)."}, 'thumbnailUrl': {'type': 'string', 'description': 'YOUTUBE: the custom thumbnail, a Hermoso-hosted image (make_thumbnail, or upload_file for the user’s own). Omit it and a frame of the video is used; "auto" keeps YouTube’s pick.'}, 'xQuotePostId': {'type': 'string', 'description': 'X — the numeric id of an X post this one QUOTES: the last part of its URL. NAMED xQuotePostId, NOT quotePostId, because `quotePostId` on this same schedule belongs to THREADS. Billed at X’s higher LINK rate.'}, 'collaborators': {'type': 'array', 'items': {'type': 'string'}, 'description': 'INSTAGRAM — a COLLAB post: up to 3 Instagram usernames invited to CO-AUTHOR it, so it appears on their profile too once they accept, with both handles in the header and the engagement shared. Handles only ("hermosoai"); a leading @ is fine. Instagram must be one of the `channels` — asking for collaborators on a schedule Instagram is not on is REFUSED now rather than discovered when it fires, and the other channels in a mixed schedule simply publish without co-authors. THE INVITE IS SENT WHEN THE POST FIRES, not when you schedule it, and it is PENDING until the other account accepts in their notifications; check with instagram_collaborators afterwards rather than telling the user it is live on both profiles.'}, 'coverImageUrl': {'type': 'string', 'description': 'THE VIDEO COVER as a picture instead of a frame — a Hermoso-hosted image (upload_file). Every channel that takes a cover image gets it (Instagram, Facebook, LinkedIn Page, Pinterest, Telegram, YouTube); TikTok takes only a frame. Never together with coverAtMs.'}, 'disableStitch': {'type': 'boolean', 'description': 'TIKTOK VIDEO ONLY — block Stitches. Same photo-post rule as disableDuet.'}, 'platformCover': {'type': 'boolean', 'description': 'VIDEO COVER. Omit it (the default) and Hermoso sets the video’s best frame — the same frame as its Library thumbnail — as the cover on every channel that allows one (Instagram, Facebook, TikTok direct posts, LinkedIn Pages, Pinterest, Telegram, YouTube) — and on X, Threads and Bluesky, which have none, a blank first frame is replaced on a copy sent there only. true = send no cover and let the platform pick (usually the first frame). A cover you pass yourself always wins.'}, 'replySettings': {'enum': ['following', 'mentionedUsers', 'subscribers', 'verified'], 'type': 'string', 'description': 'X — who may reply. Omit for everyone, which is the right default for a brand post.'}, 'brandedContent': {'type': 'boolean', 'description': 'TIKTOK — the PAID-PARTNERSHIP disclosure (TikTok’s brand_content_toggle): true when this post promotes a THIRD-PARTY business. It is a compliance declaration, not a preference — set it whenever the post is sponsored. TikTok only accepts branded content on a public or friends-only post, so it cannot ride a private/SELF_ONLY or draft TikTok item and the schedule is refused with the reason.'}, 'disableComment': {'type': 'boolean', 'description': 'TIKTOK — turn comments off on this post.'}, 'linkAttachment': {'type': 'string', 'description': 'THREADS ONLY — a full http(s) URL rendered as a link card. This is the ONLY way a Threads post carries a destination, and Threads attaches it to TEXT-ONLY posts (a post with media cannot also carry a card).'}, 'targetAudience': {'type': 'object', 'properties': {'degrees': {'type': 'array', 'items': {'type': 'string'}}, 'industries': {'type': 'array', 'items': {'type': 'string'}}, 'seniorities': {'type': 'array', 'items': {'type': 'string'}}, 'geoLocations': {'type': 'array', 'items': {'type': 'string'}}, 'jobFunctions': {'type': 'array', 'items': {'type': 'string'}}, 'fieldsOfStudy': {'type': 'array', 'items': {'type': 'string'}}, 'organizations': {'type': 'array', 'items': {'type': 'string'}}, 'staffCountRanges': {'type': 'array', 'items': {'enum': ['SIZE_1', 'SIZE_2_TO_10', 'SIZE_11_TO_50', 'SIZE_51_TO_200', 'SIZE_201_TO_500', 'SIZE_501_TO_1000', 'SIZE_1001_TO_5000', 'SIZE_5001_TO_10000', 'SIZE_10001_OR_MORE'], 'type': 'string'}}}, 'description': 'LINKEDIN COMPANY PAGE POST ONLY — show the post only to Page followers matching these facets (URNs or bare numeric ids; search_linkedin_ads_targeting finds them). LinkedIn requires the matching audience to be over 300 followers and refuses a smaller one. Personal-profile posts cannot be targeted.'}, 'linkDescription': {'type': 'string', 'description': 'FACEBOOK — override the DESCRIPTION of the link preview. Needs `link`. MEASURED 2026-09-17: Facebook only honours these when the Page’s business has VERIFIED the domain (Business Manager > Brand safety > Domains) — without that Meta refuses the post outright with “Only owners of the URL…”, so leave them out unless the brand owns and has verified that link’s domain.'}, 'paidPartnership': {'type': 'boolean', 'description': 'INSTAGRAM AND X — the PAID PARTNERSHIP label, a compliance declaration: set it when the post is sponsored, gifted or otherwise paid for. OPT-IN ONLY, never assume it on the user’s behalf. On Instagram, brandedContentSponsorIds names the brands behind it.'}, 'callToActionLink': {'type': 'string', 'description': 'FACEBOOK — where the button goes, when that is not the post’s own `link`.'}, 'coverTimestampMs': {'type': 'number', 'description': 'TIKTOK VIDEO ONLY — which frame TikTok uses as the cover, in milliseconds from the start. Omit and Hermoso uses the video’s best frame (platformCover:true leaves it to TikTok, which uses the first frame).'}, 'crossreshareToIg': {'type': 'boolean', 'description': 'THREADS ONLY — when this fires, ALSO share it to the linked Instagram account as a STORY. Refused on a Threads carousel. No confirmation exists that the Story was created, so the result says it was requested.'}, 'commercialContent': {'type': 'boolean', 'description': 'TIKTOK — the COMMERCIAL CONTENT DISCLOSURE toggle: true when the post promotes a brand, product or service at all. TikTok requires at least one of yourBrand / brandedContent alongside it, and a post declaring itself commercial without naming which kind is refused. Setting either disclosure already implies this, so it is only needed to be explicit.'}, 'instagramLocationId': {'type': 'string', 'description': 'INSTAGRAM — tag a place (called locationId on post_to_meta; locationId here is the Google Business listing). It is the NUMERIC ID OF A FACEBOOK PAGE associated with that location, not a place name and not coordinates; a non-numeric value is refused rather than sent.'}, 'visibilityByChannel': {'type': 'object', 'description': 'override visibility for one channel, e.g. { "tiktok": "draft" } to go live everywhere but stage TikTok for review', 'propertyNames': {'type': 'string'}, 'additionalProperties': {'type': 'string'}}, 'crossreshareDarkMode': {'type': 'boolean', 'description': 'THREADS ONLY — render that Instagram Story in dark mode. Needs crossreshareToIg.'}, 'linkedinOrganizationId': {'type': 'string', 'description': 'LINKEDIN — publish as a COMPANY PAGE instead of the connected personal profile. The organization id from list_linkedin_pages. Omit and it posts as the person: an unset id means the profile, never “probably the company”. A Page can also carry VIDEO, which a personal profile cannot.'}, 'brandedContentSponsorIds': {'type': 'array', 'items': {'type': 'string'}, 'description': 'INSTAGRAM — the numeric Instagram USER IDS of the brands behind that label (at most 2, and ids rather than @handles). Naming sponsors IS asking for the label, so setting these with `paidPartnership:false` is refused instead of publishing brand credits with no disclosure.'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['url'], 'properties': {'url': {'type': 'string', 'description': 'the ad asset URL (a /generated/ path or public URL)'}, 'kind': {'enum': ['image', 'video'], 'type': 'string', 'description': "'image' (default) or 'video'"}, 'intent': {'type': 'string', 'description': 'what the ad is trying to achieve, for goal-fit scoring'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'limit': {'type': 'integer', 'maximum': 9007199254740991, 'minimum': -9007199254740991, 'description': 'max ads returned (1–25, default 8)'}, 'domain': {'type': 'string', 'description': "the advertiser's domain, e.g. nike.com"}, 'region': {'type': 'string', 'description': '2-letter region, default US'}, 'advertiserId': {'type': 'string', 'description': 'Google advertiser id (AR…) when the domain is ambiguous'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['query'], 'properties': {'limit': {'type': 'integer', 'maximum': 9007199254740991, 'minimum': -9007199254740991, 'description': 'max reels returned (1–25, default 8)'}, 'query': {'type': 'string', 'description': 'keyword to search reels for'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'limit': {'type': 'integer', 'maximum': 9007199254740991, 'minimum': -9007199254740991, 'description': 'max ads returned (1–25, default 8)'}, 'company': {'type': 'string', 'description': 'advertiser company name'}, 'keyword': {'type': 'string', 'description': 'keyword across all advertisers'}, 'companyId': {'type': 'string', 'description': 'LinkedIn company id (numeric) when the name is ambiguous'}, 'countries': {'type': 'string', 'description': "CSV of 2-letter codes like 'US,CA'; omit or 'ALL' = worldwide"}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'limit': {'type': 'integer', 'maximum': 9007199254740991, 'minimum': -9007199254740991, 'description': 'max ads returned (1–25, default 8)'}, 'query': {'type': 'string', 'description': 'keyword search across ALL advertisers (use INSTEAD of companyName/pageId)'}, 'pageId': {'type': 'string', 'description': 'one advertiser’s ads by Facebook page id (most precise)'}, 'status': {'enum': ['ACTIVE', 'INACTIVE', 'ALL'], 'type': 'string', 'description': 'ACTIVE = currently running; default ALL (includes proven past winners)'}, 'country': {'type': 'string', 'description': "2-letter code or 'ALL' (default ALL)"}, 'mediaType': {'enum': ['ALL', 'IMAGE', 'VIDEO', 'MEME', 'IMAGE_AND_MEME', 'NONE'], 'type': 'string', 'description': 'filter by creative type (default ALL)'}, 'companyName': {'type': 'string', 'description': 'one advertiser’s ads by brand name'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['topic'], 'properties': {'limit': {'type': 'number', 'description': 'posts per platform, 1–60 (default 24)'}, 'topic': {'type': 'string', 'description': 'subject, brand, product or hashtag — "liquid death", "coffee", "#homecafe"'}, 'queries': {'type': 'number', 'description': 'query variants per platform, 1–4 (default 1); each is a paid search call'}, 'platforms': {'type': 'array', 'items': {'enum': ['tiktok', 'instagram', 'youtube'], 'type': 'string'}, 'description': 'default all three'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['query'], 'properties': {'limit': {'type': 'integer', 'maximum': 9007199254740991, 'minimum': -9007199254740991, 'description': 'max posts returned (1–25, default 8)'}, 'query': {'type': 'string', 'description': 'what to search Reddit for'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['query'], 'properties': {'limit': {'type': 'integer', 'maximum': 9007199254740991, 'minimum': -9007199254740991, 'description': 'max posts returned (1–25, default 8)'}, 'query': {'type': 'string', 'description': 'keyword to search Threads for'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['query'], 'properties': {'limit': {'type': 'integer', 'maximum': 9007199254740991, 'minimum': -9007199254740991, 'description': 'max videos returned (1–25, default 8)'}, 'query': {'type': 'string', 'description': 'keyword or hashtag (no # needed)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['query'], 'properties': {'limit': {'type': 'integer', 'maximum': 9007199254740991, 'minimum': -9007199254740991, 'description': 'max videos returned (1–25, default 8)'}, 'query': {'type': 'string', 'description': 'keyword to search videos for'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['enabled'], 'properties': {'enabled': {'type': 'boolean', 'description': 'true to turn auto-reload on, false to turn it off'}, 'reloadCredits': {'type': 'integer', 'maximum': 9007199254740991, 'minimum': -9007199254740991, 'description': 'how many credits to add each reload — must match a credit pack size (see buy_credits)'}, 'thresholdCredits': {'type': 'integer', 'maximum': 9007199254740991, 'minimum': -9007199254740991, 'description': 'reload when the balance drops below this many credits'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['competitors'], 'properties': {'runNow': {'type': 'boolean', 'description': 'true to run one check immediately (spends credits now) instead of waiting a week for the first one'}, 'competitors': {'type': 'array', 'items': {'type': 'object', 'required': ['name'], 'properties': {'name': {'type': 'string', 'description': 'the brand name, as it advertises'}, 'domain': {'type': 'string', 'description': 'its domain, e.g. ridge.com — required for Google Ads Transparency, and what disambiguates a common brand name on Meta'}}, 'additionalProperties': {}}, 'description': 'the brands to watch — the COMPLETE list, replacing whatever was set before. Empty array = stop watching.'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['provider', 'accountIds'], 'properties': {'provider': {'enum': ['tiktok', 'x', 'youtube', 'threads', 'bluesky', 'telegram', 'reddit', 'pinterest', 'instagram', 'meta', 'google_ads', 'linkedin', 'pinterest_ads', 'linkedin_ads', 'reddit_ads', 'apple_ads', 'microsoft_ads', 'google_business', 'google_analytics', 'snapchat_ads', 'x_ads', 'tiktok_ads', 'google_tag_manager', 'google_search_console', 'bing_webmaster'], 'type': 'string', 'description': 'which connector to scope'}, 'accountIds': {'type': 'array', 'items': {'type': 'string'}, 'description': 'the ids (from list_connector_accounts) this brand may use — an empty array shares nothing'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'chatId': {'type': 'string', 'description': 'TELEGRAM — which chat, group or channel posts go to (@username or numeric id). Without one, telegram is skipped: there is no default chat and posting to the wrong one is a public mistake.'}, 'dryRun': {'type': 'boolean', 'description': 'true (the default) = plan and preview only, queue nothing. Set false ONLY after a human has read a preview.'}, 'pageId': {'type': 'string', 'description': 'FACEBOOK / INSTAGRAM / THREADS — which connected Page to publish from (list_meta_pages). Omit for the brand’s only Page.'}, 'boardId': {'type': 'string', 'description': 'PINTEREST — which board Pins go on (list_pinterest_boards). Without one, Pinterest is skipped: a Pin on the wrong board is a public mistake, so it is never guessed.'}, 'enabled': {'type': 'boolean', 'description': 'on/off. false PAUSES it: the recurring job is deleted and nothing new is queued. Already-queued posts are untouched.'}, 'channels': {'type': 'array', 'items': {'enum': ['facebook', 'instagram', 'threads', 'tiktok', 'youtube', 'linkedin', 'x', 'pinterest', 'google_business', 'bluesky', 'telegram'], 'type': 'string'}, 'description': 'restrict it to these channels. Omit (or send an empty list) to use every connected channel that can carry each post.'}, 'daysAhead': {'type': 'number', 'description': 'how far ahead to keep the queue full, 1–30 (default 7)'}, 'postsPerDay': {'type': 'number', 'description': 'cap the posts per day BELOW the number of posting times. 0 (default) = use every posting time, which is where "3 a day" comes from. To post MORE per day, add posting times instead.'}, 'maxImagesPerDay': {'type': 'number', 'description': 'how many NEW images a day it may render when the Library runs dry. 0 (default) = none, spend nothing.'}, 'maxVideosPerDay': {'type': 'number', 'description': 'how many NEW videos a day it may render. 0 (default) = none. Video is the expensive one — hundreds of credits each.'}, 'maxCreditsPerDay': {'type': 'number', 'description': 'a hard credit ceiling per day, checked BEFORE any render starts. It binds independently of the counts above.'}, 'assetCooldownDays': {'type': 'number', 'description': 'how long before a Library render may be posted again (default 30). It never repeats one inside this window — it queues fewer posts and says so.'}, 'linkedinOrganizationId': {'type': 'string', 'description': 'LINKEDIN — which company Page to post as (list_linkedin_pages). A single shared Page is used automatically; a Page that is not shared with this brand is ignored rather than failing the whole post.'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['imageUrl'], 'properties': {'brandId': {'type': 'string', 'description': 'a brand id/name from list_brands to lock the product for; omit to use the active brand'}, 'imageUrl': {'type': 'string', 'description': 'the image URL to lock as the product (from a research result, a workspace / list_product_photos url, or any public product photo)'}, 'source_note': {'type': 'string', 'description': 'a short note on where it came from, e.g. "from their IG post"'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['email', 'role'], 'properties': {'role': {'enum': ['admin', 'member'], 'type': 'string', 'description': 'the new role'}, 'email': {'type': 'string', 'description': 'the member’s email'}, 'confirm': {'type': 'boolean', 'description': 'REQUIRED true'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['scenes'], 'properties': {'model': {'type': 'string', 'description': 'video model id from hermoso_capabilities — omit to let the router pick'}, 'voice': {'type': 'string', 'description': 'voiceover voice name, e.g. Rachel / George'}, 'scenes': {'type': 'array', 'items': {'type': 'object', 'properties': {}, 'additionalProperties': {}}, 'minItems': 2, 'description': 'array of scene objects (visual + optional voiceover/seconds)'}, 'voiceover': {'type': 'string', 'description': 'full voiceover script spoken across the scenes'}, 'resolution': {'type': 'string', 'description': '720p (default), 1080p for full detail, or 480p for a cheaper draft'}, 'aspectRatio': {'type': 'string', 'description': 'output aspect ratio, e.g. 9:16 (default) / 1:1 / 16:9'}, 'durationSeconds': {'type': 'number', 'description': 'total spot length in seconds (defaults to the sum of the scenes’ seconds)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['key'], 'properties': {'key': {'type': 'string', 'description': 'the store key to read (one of the allowlisted keys)'}, 'limit': {'type': 'number', 'description': 'max array items to return (default 50)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'pageId': {'type': 'string', 'description': 'the company Page that owns the form — from list_linkedin_pages; omit when one Page is shared'}, 'leadType': {'enum': ['SPONSORED', 'COMPANY', 'EVENT', 'ORGANIZATION_PRODUCT'], 'type': 'string', 'description': 'defaults by owner: SPONSORED for an ad account, COMPANY for a Page'}, 'forwardTo': {'type': 'string', 'description': 'optional public HTTPS URL Hermoso relays each lead event to (a CRM, Zapier, Make)'}, 'adAccountId': {'type': 'string', 'description': 'read forms owned by an AD ACCOUNT instead of a Page'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'confirm': {'type': 'boolean', 'description': 'true to APPLY the proposal; omit to only see it'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'goal': {'type': 'string', 'description': 'current marketing goal'}, 'name': {'type': 'string'}, 'sells': {'type': 'string', 'description': 'what the brand sells'}, 'style': {'type': 'string', 'description': 'visual style — palette, typography, aesthetic'}, 'voice': {'type': 'string', 'description': 'brand voice/tone'}, 'domain': {'type': 'string', 'description': 'website domain'}, 'summary': {'type': 'string', 'description': 'one-line description'}, 'audience': {'type': 'string'}, 'category': {'type': 'string'}, 'pronounce': {'type': 'string', 'description': 'how the brand NAME is said aloud, as a simple respelling with the stressed syllable in capitals (e.g. "KOH-dee-ak"). Videos use it as a delivery note beside the spoken line; set it when a render mispronounced the name.'}, 'positioning': {'type': 'string'}, 'pronunciations': {'type': 'object', 'description': 'how PRODUCT names are said aloud, e.g. {"Power Cakes": "POW-er cakes"}. Merged into the saved ones; an empty string removes one.', 'propertyNames': {'type': 'string'}, 'additionalProperties': {'type': 'string'}}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'docUrl': {'type': 'string', 'description': 'a Google Docs URL — the id is extracted from it'}, 'confirm': {'type': 'boolean'}, 'rewrite': {'type': 'string', 'description': 'replace the WHOLE body with this text ("" empties the doc)'}, 'documentId': {'type': 'string', 'description': 'the document id (from create_doc, or list_drive_files for one the user picked)'}, 'confirmCells': {'type': 'number', 'description': 'echo back the character count the unconfirmed call reported (rewrite only)'}, 'replacements': {'type': 'array', 'items': {'type': 'object', 'required': ['find'], 'properties': {'find': {'type': 'string'}, 'replace': {'type': 'string'}, 'matchCase': {'type': 'boolean'}}}, 'description': 'find/replace pairs, applied in order'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['fileId'], 'properties': {'name': {'type': 'string', 'description': 'new name'}, 'trash': {'type': 'boolean', 'description': 'true -> move to Trash; false -> restore from Trash'}, 'fileId': {'type': 'string', 'description': 'the Drive file id'}, 'moveToFolderId': {'type': 'string', 'description': 'folder id to move the file into (from create_drive_folder / list_drive_files)'}, 'removeFromFolderId': {'type': 'string', 'description': 'the old parent folder id to remove (when moving)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['fileId'], 'properties': {'name': {'type': 'string', 'description': 'new name'}, 'fileId': {'type': 'string', 'description': 'the OneDrive item id'}, 'moveToFolderId': {'type': 'string', 'description': 'folder id to move the item into (from create_onedrive_folder / list_onedrive_files)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['key'], 'properties': {'key': {'type': 'string', 'description': "the saved row's key from list_swipefile, e.g. tiktok:handle"}, 'note': {'type': 'string', 'maxLength': 2000, 'description': 'replaces the existing note; pass "" to clear it'}, 'status': {'enum': ['new', 'contacted', 'replied', 'booked', 'passed'], 'type': 'string'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'theme': {'enum': ['dark', 'light'], 'type': 'string', 'description': 'app appearance'}, 'language': {'type': 'string', 'description': 'language for generated ads, copy and answers — e.g. "English", "German", "Japanese"'}, 'watchEmail': {'type': 'boolean', 'description': 'weekly competitor-watch email on/off'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'range': {'type': 'string', 'description': 'A1 range or anchor cell, e.g. "B2:C5", "B2", or "Q3 Report!B2" (default A1)'}, 'values': {'type': 'array', 'items': {'type': 'array', 'items': {'anyOf': [{'type': 'string'}, {'type': 'number'}, {'type': 'boolean'}]}}, 'description': 'array of row arrays to write'}, 'confirm': {'type': 'boolean', 'description': 'required only when the target range already holds values'}, 'updates': {'type': 'array', 'items': {'type': 'object', 'properties': {'range': {'type': 'string'}, 'values': {'type': 'array', 'items': {'type': 'array', 'items': {'anyOf': [{'type': 'string'}, {'type': 'number'}, {'type': 'boolean'}]}}}}}, 'description': 'write SEVERAL disjoint ranges in one call, instead of range+values'}, 'sheetUrl': {'type': 'string'}, 'spreadsheetId': {'type': 'string'}, 'valueInputOption': {'enum': ['USER_ENTERED', 'RAW'], 'type': 'string', 'description': 'USER_ENTERED (default) parses formulas, dates and numbers the way typing them would; RAW stores every value as literal text'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'plan': {'type': 'string', 'description': 'the plan id to move to (e.g. pro) — omit to list the available plans first'}, 'period': {'enum': ['mo', 'yr'], 'type': 'string', 'description': 'billing cadence — monthly (default) or yearly (2 months free)'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'properties': {'url': {'type': 'string', 'description': 'a PUBLIC http(s) URL Hermoso fetches server-side (private/internal addresses are refused, and every redirect hop is re-checked). Works on every surface including the hosted connector, and the bytes never cross this connection — prefer this whenever the file is reachable on the web.'}, 'name': {'type': 'string', 'description': 'original file name — helps pick the right extension'}, 'dataUri': {'type': 'string', 'description': 'base64 data: URI of the file bytes (data:<mime>;base64,<…>) — bytes travel over this connection, so keep it small'}, 'getUploadUrl': {'type': 'boolean', 'description': 'ASK FOR A ONE-TIME UPLOAD URL instead of uploading now — use this whenever the file is on the user’s machine and you can run a shell or an HTTP request. Returns a uploadUrl you PUT the raw bytes to (any HTTP client), which answers with the durable Hermoso url. It beats `dataUri` for anything but a small image: a data: URI spends the whole file as tokens in this conversation. One file per url, and it expires.'}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['video'], 'properties': {'mode': {'enum': ['precise', 'creative'], 'type': 'string', 'description': "FLUX only — 'creative' turns on its detail-enhancement pass; default precise"}, 'video': {'type': 'string', 'description': 'the source video URL'}, 'engine': {'enum': ['standard', 'flux'], 'type': 'string', 'description': "default 'standard', the precision upscaler. 'flux' = the FLUX 3 video upscaler"}}}
입력 스키마
{'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['brand'], 'properties': {'brand': {'type': 'string', 'description': 'brand id (e.g. default / p_xxx), its exact name from list_brands, or the profile id of a workspace shared with you'}}}
최근 도구 변경
유사한 MCP 서버
Heista
Analyzes video advertising, builds brand intelligence profiles, researches markets, and generates advertising scripts and creativ…
Spook SEO
Provides SEO analysis, keyword and content generation, AEO checks, ad copy tools and automated article generation and publishing.
VarynForge
Supports SEO research and content planning through niche analysis, competitor and keyword research, opportunity discovery, and wr…
AfterLaunch: the agentic growth marketing engine
Runs growth-marketing workflows covering SEO, AI-search visibility, competitor analysis, content planning, distribution channels,…
RoboWrite
Supports brand setup, content briefs, AI-generated drafts, editorial review, quality checks, and publishing workflows.
ai-content-drop
Generates AI images, videos, UGC clips, avatars, and finished ads, while organizing creative assets and campaign boards.
Newswire Com
Searches and retrieves recent press releases from Newswire.com's public newsroom feed.
Prnewswire
Searches and retrieves recent press releases from PR Newswire's public all-releases feed.