{"authentication":{"bearer":{"description":"Alternative: personal API key (Settings → API or POST /api/me/api-key). Both OAuth and API key need the Starter plan or above.","get_token":"https://cliponit.app/billing","header":"Authorization","scheme":"Bearer","token_prefix":"ck_"},"description":"OAuth 2.1 (dynamic client registration or client ID metadata document + PKCE S256): Claude.ai / Claude Desktop / Claude Code / ChatGPT connect in one click from the server URL. Starter plan and above.","methods":["oauth2","bearer"],"oauth2":{"authorization_endpoint":"https://cliponit.app/oauth/authorize","authorization_server_metadata":"https://cliponit.app/.well-known/oauth-authorization-server","client_id_metadata_document":true,"dynamic_client_registration":true,"flow":"authorization_code","pkce":"S256","protected_resource_metadata":"https://cliponit.app/.well-known/oauth-protected-resource","registration_endpoint":"https://cliponit.app/oauth/register","scopes":["clips"],"token_endpoint":"https://cliponit.app/oauth/token"},"required":true,"type":"oauth2"},"capabilities":{"prompts":false,"resources":false,"roots":false,"sampling":false,"tools":true},"description":"Two products in one server: (1) turn long videos into short, subtitled, reframed clips: import (file or URL), detect viral moments, auto-reframe on faces, cut silences, subtitles, B-roll, music, emojis, export 9:16 or 16:9 MP4; (2) compose vertical AI explainer videos from a topic, a text or a voice recording (explainer_* tools, explainer_produce end to end).","documentation":"https://cliponit.app/docs","endpoints":{"streamable_http":"https://cliponit.app/mcp"},"llms_txt":"https://cliponit.app/llms-api.txt","mcp_version":"1.0","name":"io.github.MattJeff/cliponit","openapi":"https://cliponit.app/openapi.json","pricing":"https://cliponit.app/billing","remotes":[{"type":"streamable-http","url":"https://cliponit.app/mcp"}],"serverInfo":{"name":"ClipOnIt","version":"0.1.0"},"server_name":"ClipOnIt","server_version":"0.1.0","tools":[{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"List videos"},"description":"List the user's videos, newest first: id, title, status (processing|ready|failed), state (queued|running|ready|failed), queue_position, eta_s, progress (downloading|probing|transcribing|detecting while processing), duration_s (seconds), clips_count, is_demo (1 = sample video, not exportable on free). Start here to find a video_id.","inputSchema":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"name":"list_videos","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"List videos"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Upload video file"},"description":"Upload a local video file from the server's filesystem (mp4/mov/webm; path under $HOME, $TMPDIR, /tmp or data/) and start processing (transcription + clip detection). Same quota rules as the UI. Returns the video (state queued|running); then call wait_video.","inputSchema":{"additionalProperties":false,"properties":{"brief":{"description":"Free text (≤ 1000 chars): what you want, e.g. 'clips that lead to the Mansion Club, no car-only clips'.","type":"string"},"goal":{"description":"What the clips are for: views (reach), clients (sell the offer: proof, method, CTA to the offer), comments (debate), followers (story, series). Changes which moments are picked, how they are scored (goal_fit sub-score), the titles, and adds a caption CTA per clip (clip.cta).","enum":["views","clients","comments","followers"],"type":"string"},"path":{"description":"Absolute path to a .mp4/.mov/.webm file on the server","type":"string"},"resources":{"description":"Optional. {kind:'site', url:'https://…'}: the offer's site, read server-side to align the CTA with the real offer. {kind:'video', video_id}: another of your videos, usable as B-roll (illustrate with source_video_id).","items":{"properties":{"kind":{"enum":["site","video"],"type":"string"},"url":{"type":"string"},"video_id":{"type":"string"}},"required":["kind"],"type":"object"},"maxItems":5,"type":"array"}},"required":["path"],"type":"object"},"name":"upload_video_file","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Upload video file"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":true,"readOnlyHint":false,"title":"Import video url"},"description":"Import a video from a public URL (YouTube, Vimeo, direct mp4…, via yt-dlp, max 1080p) and start transcription + clip detection. Counts against the monthly quota (HTTP 402 when reached). Returns the video (state queued|running, progress downloading); then call wait_video until state is ready (1-5 min for 20 min of video) or failed (error field). Example url: https://www.youtube.com/watch?v=e7Zl5VWejQc","inputSchema":{"additionalProperties":false,"properties":{"brief":{"description":"Free text (≤ 1000 chars): what you want, e.g. 'clips that lead to the Mansion Club, no car-only clips'.","type":"string"},"goal":{"description":"What the clips are for: views (reach), clients (sell the offer: proof, method, CTA to the offer), comments (debate), followers (story, series). Changes which moments are picked, how they are scored (goal_fit sub-score), the titles, and adds a caption CTA per clip (clip.cta).","enum":["views","clients","comments","followers"],"type":"string"},"resources":{"description":"Optional. {kind:'site', url:'https://…'}: the offer's site, read server-side to align the CTA with the real offer. {kind:'video', video_id}: another of your videos, usable as B-roll (illustrate with source_video_id).","items":{"properties":{"kind":{"enum":["site","video"],"type":"string"},"url":{"type":"string"},"video_id":{"type":"string"}},"required":["kind"],"type":"object"},"maxItems":5,"type":"array"},"url":{"description":"Public video URL","type":"string"}},"required":["url"],"type":"object"},"name":"import_video_url","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Import video url"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Set video objective"},"description":"Set (replace) the clip objective of a video: goal, brief, resources (absent field = cleared). It applies to the next detection: pass regenerate:true to re-detect the clips now with it (transcript reused, no new quota). Returns {goal, brief, resources, site_summary (the site as read, null until a detection ran), playbook_version}.","inputSchema":{"additionalProperties":false,"properties":{"brief":{"description":"Free text (≤ 1000 chars): what you want, e.g. 'clips that lead to the Mansion Club, no car-only clips'.","type":"string"},"goal":{"description":"What the clips are for: views (reach), clients (sell the offer: proof, method, CTA to the offer), comments (debate), followers (story, series). Changes which moments are picked, how they are scored (goal_fit sub-score), the titles, and adds a caption CTA per clip (clip.cta).","enum":["views","clients","comments","followers"],"type":"string"},"regenerate":{"default":false,"description":"Re-detect the clips now with this objective (replaces the current clips)","type":"boolean"},"resources":{"description":"Optional. {kind:'site', url:'https://…'}: the offer's site, read server-side to align the CTA with the real offer. {kind:'video', video_id}: another of your videos, usable as B-roll (illustrate with source_video_id).","items":{"properties":{"kind":{"enum":["site","video"],"type":"string"},"url":{"type":"string"},"video_id":{"type":"string"}},"required":["kind"],"type":"object"},"maxItems":5,"type":"array"},"video_id":{"description":"Video id (UUID)","type":"string"}},"required":["video_id"],"type":"object"},"name":"set_video_objective","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Set video objective"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Get video"},"description":"Get a video (status, state queued|running|ready|failed, queue_position, eta_s = estimated seconds left, progress step, duration_s, error) with its detected clips once ready, sorted chronologically. Each clip has id, title, start_s/end_s (absolute seconds in the source), score (0-100 viral potential), reason (why), description and hashtags (null until describe_clip), cta (caption call to action, only when the video has a goal), thumbnail_url. Prefer wait_video over polling this.","inputSchema":{"additionalProperties":false,"properties":{"video_id":{"description":"Video id (UUID)","type":"string"}},"required":["video_id"],"type":"object"},"name":"get_video","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Get video"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Wait video"},"description":"Block (server-side, up to timeout_s ≤ 120) until the video's state is ready or failed, then return it exactly like get_video (clips included). If still queued/running at the timeout, returns the video with timed_out:true, eta_s and queue_position: just call it again. Use this instead of polling get_video.","inputSchema":{"additionalProperties":false,"properties":{"timeout_s":{"default":120.0,"description":"Max seconds to block (server-side long-poll); the object is returned with timed_out:true if still in progress","maximum":120.0,"minimum":1,"type":"number"},"video_id":{"description":"Video id (UUID)","type":"string"}},"required":["video_id"],"type":"object"},"name":"wait_video","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Wait video"},{"annotations":{"destructiveHint":true,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":false,"title":"Delete video"},"description":"Delete a video with its clips, edits, exports and files. Irreversible. Returns {ok:true}.","inputSchema":{"additionalProperties":false,"properties":{"video_id":{"description":"Video id (UUID)","type":"string"}},"required":["video_id"],"type":"object"},"name":"delete_video","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Delete video"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Find moment"},"description":"Find a moment in a ready video from a natural-language query (AI search over the transcript), e.g. 'the part about pricing'. Creates and returns a new clip {id, title, start_s, end_s} (absolute seconds) appended to the video's clips (delete_clip to remove it). Requires state ready. Free plan: 5 searches/day (HTTP 402 beyond).","inputSchema":{"additionalProperties":false,"properties":{"query":{"description":"What to look for, e.g. 'the part about pricing'","type":"string"},"video_id":{"description":"Video id (UUID)","type":"string"}},"required":["video_id","query"],"type":"object"},"name":"find_moment","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Find moment"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Create clip"},"description":"Create a clip by hand on a ready video from exact bounds (absolute seconds in the source, 3 ≤ end_s - start_s ≤ 180). Bounds within 0.6 s of a transcript sentence start/end are snapped to it, else to the nearest word boundary within 0.6 s (snap:false keeps them as given), then rounded to 0.01 s. Without title, the clip is returned at once with its first words as title and an AI title is written a few seconds later (get_video to read it). The clip is inserted at its chronological position (later clips move down one rank) and returned with its thumbnail_url; score stays null. Does not count against the monthly video quota. Tip: pick bounds from get_transcript word timings. HTTP 400 when out of range or the video is not ready, 404 when the video is not yours.","inputSchema":{"additionalProperties":false,"properties":{"end_s":{"description":"End, absolute seconds (3 to 180 s after start_s)","type":"number"},"snap":{"default":true,"description":"Snap bounds to sentence starts/ends (else word boundaries) within 0.6 s","type":"boolean"},"start_s":{"description":"Start, absolute seconds","minimum":0,"type":"number"},"title":{"description":"Optional title (max 80 characters); AI-generated when omitted","type":"string"},"video_id":{"description":"Video id (UUID)","type":"string"}},"required":["video_id","start_s","end_s"],"type":"object"},"name":"create_clip","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Create clip"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Get clip"},"description":"Get a clip with its edit state for an orientation: layout, keyframes [{t (seconds from clip start), layout, zones:[{x,y,w,h} fractions 0..1 of the source]}], cuts [{start,end} seconds], subtitles settings, broll, music, emojis.","inputSchema":{"additionalProperties":false,"properties":{"clip_id":{"description":"Clip id (UUID)","type":"string"},"orientation":{"default":"vertical","description":"Target orientation of the edit/export: vertical (9:16, 1080x1920), horizontal (16:9), square (1:1, 1080x1080), portrait45 (4:5, 1080x1350). Each orientation has its own independent edit.","enum":["vertical","horizontal","square","portrait45"],"type":"string"}},"required":["clip_id"],"type":"object"},"name":"get_clip","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Get clip"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Illustrate clip with video"},"description":"B-roll from the user's OWN footage: cut over the speaker, full frame, shots taken from OTHER moments of the same source video or from the account's B-roll library (all their videos) that show what is being said (e.g. the car when they talk about the car). First call on a video indexes its shots (one keyframe per shot described by a light vision model, ~30-60 s, ~0.1 $, then cached). Rules: never a shot already inside the clip, never a talking-head shot, 1.5-2.5 s each, at most one every 4 s, no overlap with existing B-roll; the voice keeps playing. Inserts are added to the clip's broll for that orientation (src:'video', source_s = where the shot starts in the source) and can be removed like any B-roll. Returns {broll (full list), inserts:[{start, end, source_s, shot, word, description}], shots_indexed}. Paid plans (403 on free).","inputSchema":{"additionalProperties":false,"properties":{"clip_id":{"description":"Clip id (UUID)","type":"string"},"orientation":{"default":"vertical","description":"Target orientation of the edit/export: vertical (9:16, 1080x1920), horizontal (16:9), square (1:1, 1080x1080), portrait45 (4:5, 1080x1350). Each orientation has its own independent edit.","enum":["vertical","horizontal","square","portrait45"],"type":"string"}},"required":["clip_id"],"type":"object"},"name":"illustrate_clip_with_video","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Illustrate clip with video"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Broll search"},"description":"Search the account's B-roll library by words (description and tags, accent-insensitive, any language of the video + English): reusable shots extracted from ALL the user's videos (no talking head, bright and sharp enough, 1.5-6 s), e.g. q='Aston rouge nuit' or 'car driving highway'. Returns {items:[{id, video_id, video_title, start_s, end_s, duration_s, description, tags, score (0..1), favorite, vertical_url (720x1280 mp4), horizontal_url (1280x720 mp4), thumb_url}], total, videos, max}, best matches first. Then broll_add_to_clip.","inputSchema":{"additionalProperties":false,"properties":{"limit":{"default":20,"type":"integer"},"q":{"description":"Words to look for","type":"string"},"video_id":{"description":"Only shots from this video","type":"string"}},"required":["q"],"type":"object"},"name":"broll_search","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Broll search"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Broll list"},"description":"List the account's B-roll library (favorites first, then by quality score): same items as broll_search, plus videos:[{id, title, count, status: none|running|done|failed}] (extraction runs automatically after processing on paid plans). Filters: video_id, tag, favorite. max = library cap on the free plan (null = per-video cap only).","inputSchema":{"additionalProperties":false,"properties":{"favorite":{"type":"boolean"},"limit":{"default":100,"type":"integer"},"tag":{"type":"string"},"video_id":{"type":"string"}},"required":[],"type":"object"},"name":"broll_list","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Broll list"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Broll add to clip"},"description":"Place a shot of the B-roll library (from any of the user's videos) on a clip as a full-frame, muted cutaway on [start, end] (absolute seconds inside the clip; end omitted = shot length, 3 s max). Returns the clip's broll list for that orientation. Paid plans.","inputSchema":{"additionalProperties":false,"properties":{"clip_id":{"description":"Clip id (UUID)","type":"string"},"end":{"description":"Absolute seconds, inside the clip (optional)","type":"number"},"item_id":{"description":"id from broll_search / broll_list","type":"string"},"orientation":{"default":"vertical","description":"Target orientation of the edit/export: vertical (9:16, 1080x1920), horizontal (16:9), square (1:1, 1080x1080), portrait45 (4:5, 1080x1350). Each orientation has its own independent edit.","enum":["vertical","horizontal","square","portrait45"],"type":"string"},"start":{"description":"Absolute seconds, inside the clip","type":"number"}},"required":["clip_id","item_id","start"],"type":"object"},"name":"broll_add_to_clip","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Broll add to clip"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"List video shots"},"description":"List the shots of the clip's source video usable as B-roll for this clip (indexes the video on first call, see illustrate_clip_with_video): {shots:[{idx, start_s, end_s, description, keywords, thumb_url}], total}. Talking-head shots and shots inside the clip are left out. Then add_video_broll to place one.","inputSchema":{"additionalProperties":false,"properties":{"clip_id":{"description":"Clip id (UUID)","type":"string"}},"required":["clip_id"],"type":"object"},"name":"list_video_shots","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"List video shots"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Add video broll"},"description":"Place a moment of the clip's own source video as a full-frame B-roll: the source from source_s for (end - start) seconds, muted, covers the clip on [start, end] (absolute seconds inside the clip, 10 s max). shot = idx from list_video_shots (optional, labels the insert). Returns the clip's broll list.","inputSchema":{"additionalProperties":false,"properties":{"clip_id":{"description":"Clip id (UUID)","type":"string"},"end":{"description":"Absolute seconds, inside the clip","type":"number"},"orientation":{"default":"vertical","description":"Target orientation of the edit/export: vertical (9:16, 1080x1920), horizontal (16:9), square (1:1, 1080x1080), portrait45 (4:5, 1080x1350). Each orientation has its own independent edit.","enum":["vertical","horizontal","square","portrait45"],"type":"string"},"shot":{"description":"Optional shot idx from list_video_shots","type":"integer"},"source_s":{"description":"Where to take the footage in the source video (seconds)","minimum":0,"type":"number"},"start":{"description":"Absolute seconds, inside the clip","type":"number"}},"required":["clip_id","start","end","source_s"],"type":"object"},"name":"add_video_broll","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Add video broll"},{"annotations":{"destructiveHint":true,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":false,"title":"Delete clip"},"description":"Delete a clip (its edits, exports and files; running exports are canceled). The video keeps its other clips. Returns {ok:true}.","inputSchema":{"additionalProperties":false,"properties":{"clip_id":{"description":"Clip id (UUID)","type":"string"}},"required":["clip_id"],"type":"object"},"name":"delete_clip","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Delete clip"},{"annotations":{"destructiveHint":true,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":false,"title":"Set clip bounds"},"description":"Move the clip's start_s/end_s (absolute seconds in the source video, duration ≥ 3 s). Keyframes are shifted, cuts/B-roll/emojis outside the new range are dropped, the clip thumbnail/filmstrip is regenerated. Returns the clip. Tip: pick bounds from get_transcript word timings, or call snap_clip_to_words afterwards.","inputSchema":{"additionalProperties":false,"properties":{"clip_id":{"description":"Clip id (UUID)","type":"string"},"end_s":{"type":"number"},"start_s":{"minimum":0,"type":"number"}},"required":["clip_id","start_s","end_s"],"type":"object"},"name":"set_clip_bounds","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Set clip bounds"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":false,"title":"Snap clip to words"},"description":"Snap the clip's start_s to the nearest word start and end_s to the nearest word end in the transcript (segment bounds when word timings are missing), so the clip does not cut a word. No-op if the result would be shorter than 3 s. Returns the clip.","inputSchema":{"additionalProperties":false,"properties":{"clip_id":{"description":"Clip id (UUID)","type":"string"}},"required":["clip_id"],"type":"object"},"name":"snap_clip_to_words","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Snap clip to words"},{"annotations":{"destructiveHint":true,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Update clip edit"},"description":"Patch the edit state of a clip for an orientation: only the given fields change, the rest is kept; subtitles is merged field by field (e.g. {subtitles:{style:'boxed'}} keeps color/size/…). keyframes: [{t (seconds from clip start), layout, zones:[{x,y,w,h} fractions 0..1], ease?: cut|smooth}] replaces the list; cuts: [{start,end}] in absolute source seconds replaces the list; broll/emojis replace their list, music: null removes it (Starter+). Returns the full stored edit. 400 names the invalid field.","inputSchema":{"additionalProperties":false,"properties":{"broll":{"description":"[{id, start, end, src, path, place?}] as returned by get_clip (absolute seconds). place {x,y,w,h} = fractions of the OUTPUT frame; omit it for a full-frame cutaway, set {\"x\":0,\"y\":0.5,\"w\":1,\"h\":0.5} with layout `duo` for the vertical duo (second video in the bottom half)","items":{"type":"object"},"type":"array"},"clip_id":{"description":"Clip id (UUID)","type":"string"},"cuts":{"items":{"properties":{"end":{"type":"number"},"start":{"type":"number"}},"required":["start","end"],"type":"object"},"type":"array"},"emojis":{"items":{"properties":{"emoji":{"type":"string"},"end":{"type":"number"},"size":{"enum":[1,2,3],"type":"integer"},"start":{"type":"number"},"x":{"type":"number"},"y":{"type":"number"}},"required":["start","end","emoji"],"type":"object"},"type":"array"},"keyframes":{"items":{"properties":{"ease":{"enum":["cut","smooth"],"type":"string"},"layout":{"type":"string"},"t":{"minimum":0,"type":"number"},"zones":{"items":{"properties":{"h":{"type":"number"},"w":{"type":"number"},"x":{"type":"number"},"y":{"type":"number"}},"required":["x","y","w","h"],"type":"object"},"type":"array"}},"required":["t","layout","zones"],"type":"object"},"type":"array"},"layout":{"enum":["vertical","horizontal","split","trio","spotlight","centered","duo","keynote"],"type":"string"},"music":{"properties":{"ducking":{"default":true,"type":"boolean"},"fade_s":{"default":1.5,"maximum":10,"minimum":0,"type":"number"},"music_id":{"type":"string"},"volume":{"default":0.25,"maximum":1,"minimum":0,"type":"number"}},"type":["object","null"]},"orientation":{"default":"vertical","description":"Target orientation of the edit/export: vertical (9:16, 1080x1920), horizontal (16:9), square (1:1, 1080x1080), portrait45 (4:5, 1080x1350). Each orientation has its own independent edit.","enum":["vertical","horizontal","square","portrait45"],"type":"string"},"subtitles":{"additionalProperties":false,"description":"Burned-in subtitles; every field optional, merged over the current values","properties":{"animate":{"default":false,"description":"Pop animation on the current word","type":"boolean"},"bw":{"default":false,"description":"Black & white grade of the whole image (recommended with behind)","type":"boolean"},"case":{"default":"normal","enum":["normal","upper","lower"],"type":"string"},"color":{"default":"yellow","description":"Accent colour (current word in karaoke, text otherwise)","enum":["yellow","white","green","cyan","pink","orange","rainbow"],"type":"string"},"enabled":{"default":true,"type":"boolean"},"font":{"default":"Inter","enum":["Inter","Montserrat","Bangers","Poppins","Arial"],"type":"string"},"hook":{"description":"Title card shown at the top during the first seconds (the hook). text null = clip title.","properties":{"duration_s":{"default":2.5,"maximum":10,"minimum":0.5,"type":"number"},"enabled":{"default":false,"type":"boolean"},"style":{"default":"card","description":"card = rounded box, plain = text with outline","enum":["card","plain"],"type":"string"},"text":{"maxLength":120,"type":["string","null"]}},"type":"object"},"lang":{"default":"original","description":"'original' or an ISO 639-1 code to translate (paid plans)","type":"string"},"overrides":{"description":"Replace the text of the segment covering [start,end] (absolute seconds; paid plans)","items":{"properties":{"end":{"type":"number"},"start":{"type":"number"},"text":{"type":"string"}},"required":["start","end","text"],"type":"object"},"type":"array"},"position":{"default":"bottom","enum":["bottom","middle","top"],"type":"string"},"size":{"default":2,"description":"52/64/80 px on a 1080x1920 output","enum":[1,2,3],"type":"integer"},"style":{"default":"karaoke","description":"behind = one giant serif word behind the person (subject mask, Premium/Montage pro), serif = same word on top, hollow = outlined caps, highlight = spoken word in a colour box","enum":["karaoke","boxed","outline","glow","minimal","behind","serif","hollow","highlight"],"type":"string"},"words_per_line":{"default":5,"maximum":8,"minimum":3,"type":"integer"}},"type":"object"}},"required":["clip_id"],"type":"object"},"name":"update_clip_edit","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Update clip edit"},{"annotations":{"destructiveHint":true,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":false,"title":"Set default subtitles"},"description":"Set the account's subtitle preset, applied to every clip edit created from now on (new clips, new orientations, exports of untouched clips). Fields are merged over the built-in defaults ({} resets), existing edits are not changed (use update_clip_edit for those). Returns the stored preset. Call it once before export_all_clips.","inputSchema":{"additionalProperties":false,"properties":{"subtitles":{"additionalProperties":false,"description":"Burned-in subtitles; every field optional, merged over the current values","properties":{"animate":{"default":false,"description":"Pop animation on the current word","type":"boolean"},"bw":{"default":false,"description":"Black & white grade of the whole image (recommended with behind)","type":"boolean"},"case":{"default":"normal","enum":["normal","upper","lower"],"type":"string"},"color":{"default":"yellow","description":"Accent colour (current word in karaoke, text otherwise)","enum":["yellow","white","green","cyan","pink","orange","rainbow"],"type":"string"},"enabled":{"default":true,"type":"boolean"},"font":{"default":"Inter","enum":["Inter","Montserrat","Bangers","Poppins","Arial"],"type":"string"},"hook":{"description":"Title card shown at the top during the first seconds (the hook). text null = clip title.","properties":{"duration_s":{"default":2.5,"maximum":10,"minimum":0.5,"type":"number"},"enabled":{"default":false,"type":"boolean"},"style":{"default":"card","description":"card = rounded box, plain = text with outline","enum":["card","plain"],"type":"string"},"text":{"maxLength":120,"type":["string","null"]}},"type":"object"},"lang":{"default":"original","description":"'original' or an ISO 639-1 code to translate (paid plans)","type":"string"},"overrides":{"description":"Replace the text of the segment covering [start,end] (absolute seconds; paid plans)","items":{"properties":{"end":{"type":"number"},"start":{"type":"number"},"text":{"type":"string"}},"required":["start","end","text"],"type":"object"},"type":"array"},"position":{"default":"bottom","enum":["bottom","middle","top"],"type":"string"},"size":{"default":2,"description":"52/64/80 px on a 1080x1920 output","enum":[1,2,3],"type":"integer"},"style":{"default":"karaoke","description":"behind = one giant serif word behind the person (subject mask, Premium/Montage pro), serif = same word on top, hollow = outlined caps, highlight = spoken word in a colour box","enum":["karaoke","boxed","outline","glow","minimal","behind","serif","hollow","highlight"],"type":"string"},"words_per_line":{"default":5,"maximum":8,"minimum":3,"type":"integer"}},"type":"object"}},"required":["subtitles"],"type":"object"},"name":"set_default_subtitles","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Set default subtitles"},{"annotations":{"destructiveHint":true,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Auto reframe"},"description":"Detect faces/speakers, camera cuts, the audio speech level and presentation slides in the clip and write layout + keyframes for the orientation. mode=auto (default): keynote layout if a slide + a speaker are detected, otherwise speaker_tracking; mode=speaker_tracking (podcasts): vertical on the person who is TALKING (audio level x mouth movement at 10 fps), clean cut at each turn (held >= 1.4 s), split top/bottom during rapid back-and-forth; mode=keynote_split: slide on top (16:9) + speaker below (layout `keynote`); mode=keynote_speaker: speaker only, slide cropped out; mode=follow (legacy): vertical following the active speaker, split while the clip's stable duo is on screen; mode=mix (auto layout, vertical only): layout picked shot by shot — 1 face vertical, 2 faces split (or vertical on someone talking alone >= 4 s), 3 faces trio, no usable face or a group -> full-frame wide shot on blurred background. Shots without faces never get a crop on the void. framing: tight (face close-up), medium (default, face + shoulders), wide (least zoom); zoom is capped to avoid grain. horizontal = full frame. Synchronous, ~5 s per 30 s of clip; call it before export for talking-head videos. Overwrites existing keyframes (including auto_zoom ones: run auto_reframe first, auto_zoom after). Returns {layout, keyframes, faces, speakers, shots, mode (applied), slides}.","inputSchema":{"additionalProperties":false,"properties":{"clip_id":{"description":"Clip id (UUID)","type":"string"},"framing":{"default":"medium","description":"How close the crop is: tight close-up, medium face + shoulders, wide least zoom","enum":["tight","medium","wide"],"type":"string"},"mode":{"default":"auto","description":"auto = keynote if slides detected else speaker_tracking; speaker_tracking = frame whoever talks; keynote_split = slide on top + speaker; keynote_speaker = speaker only; follow = legacy tracking; mix = mixed auto layout (vertical/split/trio/wide shot) per shot, 9:16 only","enum":["auto","speaker_tracking","keynote_split","keynote_speaker","follow","mix"],"type":"string"},"orientation":{"default":"vertical","description":"Target orientation of the edit/export: vertical (9:16, 1080x1920), horizontal (16:9), square (1:1, 1080x1080), portrait45 (4:5, 1080x1350). Each orientation has its own independent edit.","enum":["vertical","horizontal","square","portrait45"],"type":"string"}},"required":["clip_id"],"type":"object"},"name":"auto_reframe","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Auto reframe"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Detect silences"},"description":"Detect silences in the clip (audio energy, 150 Hz high-pass, trimmed 80 ms each side). Without threshold_db the threshold adapts to the background noise (car, street). Returns {cuts:[{start,end,kind:'silence'}], threshold_db} in absolute source seconds; does NOT modify the edit: pass them (merged with the existing cuts from get_clip) to update_clip_edit.cuts to apply. Prefer remove_filler_words, which also cuts ums and repeats.","inputSchema":{"additionalProperties":false,"properties":{"clip_id":{"description":"Clip id (UUID)","type":"string"},"min_ms":{"default":400,"description":"Minimum silence length in milliseconds","type":"integer"},"threshold_db":{"description":"Fixed silence threshold in dB (omit = adaptive)","type":"number"}},"required":["clip_id"],"type":"object"},"name":"detect_silences","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Detect silences"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Remove filler words"},"description":"Find ums/uhs (filler), hesitations, repeated words, false starts, verbal tics (aggressive only) and dead air in the clip, from a verbatim word-level transcription + audio energy. Cuts never fall inside a kept word and leave a natural breath. Returns {cuts:[{start,end,kind,confidence,text}], source, removed_s, duration_s, applied} (absolute source seconds). apply:true replaces the previous automatic cuts of the orientation's edit (manual cuts are kept); each cut can then be removed individually with update_clip_edit.cuts. ~5 s per minute of clip.","inputSchema":{"additionalProperties":false,"properties":{"apply":{"default":false,"type":"boolean"},"clip_id":{"description":"Clip id (UUID)","type":"string"},"intensity":{"default":"normal","enum":["light","normal","aggressive"],"type":"string"},"orientation":{"default":"vertical","description":"Target orientation of the edit/export: vertical (9:16, 1080x1920), horizontal (16:9), square (1:1, 1080x1080), portrait45 (4:5, 1080x1350). Each orientation has its own independent edit.","enum":["vertical","horizontal","square","portrait45"],"type":"string"}},"required":["clip_id"],"type":"object"},"name":"remove_filler_words","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Remove filler words"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Auto zoom"},"description":"Add punch-in zoom keyframes at sentence starts (single-zone layouts only) on top of the current keyframes and save them. Run it AFTER auto_reframe (which overwrites keyframes). Returns {keyframes}.","inputSchema":{"additionalProperties":false,"properties":{"clip_id":{"description":"Clip id (UUID)","type":"string"},"every":{"default":2,"description":"Zoom on every Nth sentence","type":"integer"},"orientation":{"default":"vertical","description":"Target orientation of the edit/export: vertical (9:16, 1080x1920), horizontal (16:9), square (1:1, 1080x1080), portrait45 (4:5, 1080x1350). Each orientation has its own independent edit.","enum":["vertical","horizontal","square","portrait45"],"type":"string"},"strength":{"default":0.15,"description":"Zoom amount as a fraction 0..1 of the frame","type":"number"}},"required":["clip_id"],"type":"object"},"name":"auto_zoom","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Auto zoom"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Describe clip"},"description":"Generate (or return cached) a ready-to-post description and 5-8 hashtags (without #) for the clip from its transcript. Returns {description, hashtags}.","inputSchema":{"additionalProperties":false,"properties":{"clip_id":{"description":"Clip id (UUID)","type":"string"}},"required":["clip_id"],"type":"object"},"name":"describe_clip","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Describe clip"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Export clip"},"description":"Render the clip as MP4 with its current edit for the orientation (subtitles — account preset if the clip was never edited —, cuts, reframing, B-roll, music, emojis; watermark on free). Returns the export {id, status: queued, eta_s}; then wait_export. Demo video on the free plan: HTTP 403.","inputSchema":{"additionalProperties":false,"properties":{"clip_id":{"description":"Clip id (UUID)","type":"string"},"music":{"description":"false = « Sans musique » : no music added by ClipOnIt (the clip's own sound/voice is kept, loudness unchanged); the user then adds a trending licensed sound in TikTok / Instagram. Remembered with the clip edit.","type":"boolean"},"orientation":{"default":"vertical","description":"Target orientation of the edit/export: vertical (9:16, 1080x1920), horizontal (16:9), square (1:1, 1080x1080), portrait45 (4:5, 1080x1350). Each orientation has its own independent edit.","enum":["vertical","horizontal","square","portrait45"],"type":"string"},"style":{"default":"standard","description":"standard = the edit as is. premium (paid plans) = edited for the scroll at render time, the edit is not modified: white hook card on the first 3 s quoting a sentence actually said in the clip (else the clip title), big uppercase 3-word captions with keywords in yellow, ums and silences cut, illustration shots taken from the video, light punch-ins on keywords, no caption when nobody speaks, audio at -14 LUFS.","enum":["standard","premium"],"type":"string"},"tics":{"description":"Also remove ums, hesitations, repeats and dead air just before rendering (same as remove_filler_words apply:true; manual cuts kept).","enum":["light","normal","aggressive"],"type":"string"}},"required":["clip_id"],"type":"object"},"name":"export_clip","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Export clip"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Montage templates"},"description":"Montage (paid plans): list ClipOnIt's house viral formats (tier list, two-column sort, paid vs free, rated out of 10, wrong vs right split screen, before/after, symptom → cause, easy to hard, 3 mistakes, A vs B, one image per idea…). Each has an id, FR/EN name and description, and its editing recipe (JSON, schema at GET /api/montage/schema). No AI cost.","inputSchema":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"name":"montage_templates","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Montage templates"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Montage upload rush"},"description":"Montage: upload one rush (video ≤ 2 min or image, local path under $HOME, $TMPDIR, /tmp or data/) to the account's private media. Returns {id, …}: pass ids as media_ids to montage_create. Rushes stay private to the account.","inputSchema":{"additionalProperties":false,"properties":{"path":{"type":"string"}},"required":["path"],"type":"object"},"name":"montage_upload_rush","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Montage upload rush"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Montage search references"},"description":"Montage: find 6-12 real public short videos (YouTube Shorts) for a topic/format (« tier list nutrition »). Returns titles, thumbnails and links to the originals (never re-hosted). Pass one url as reference_url to montage_create: only its structure is analysed, the file is deleted. No AI cost.","inputSchema":{"additionalProperties":false,"properties":{"query":{"maxLength":80,"minLength":2,"type":"string"}},"required":["query"],"type":"object"},"name":"montage_search_references","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Montage search references"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Montage shared recipes"},"description":"Montage: editing recipes already analysed from references (shared library, structure only, nothing from any account). Reuse one as `recipe` in montage_create: 0 AI cost.","inputSchema":{"additionalProperties":false,"properties":{"family":{"description":"tiers | columns | pairs | score | versus | steps | list | demo | free","type":"string"}},"required":[],"type":"object"},"name":"montage_shared_recipes","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Montage shared recipes"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Montage create"},"description":"Montage (paid plans, counts 1 video of the monthly quota): edit the user's rushes into a vertical 9:16 video following ONE structure — template_id (montage_templates), reference_url (TikTok / Instagram Reels / Facebook / YouTube Shorts link: cuts, layout, texts, captions, tempo are measured, then the file is deleted; already-analysed references are reused for free), or recipe (JSON). intent = the user's sentence (« a demo of my app with its logo and music »). Word-by-word captions, the account logo (Settings → Logo) and our own royalty-free music are added by the export. rights must be true: only rushes the user holds the rights to. analyze_only + reference_url = just return the recipe. Then montage_wait: {status, url (mp4), edit_url (editor), report {cost_usd, warnings, shots, duration_s}}.","inputSchema":{"additionalProperties":false,"properties":{"analyze_only":{"type":"boolean"},"intent":{"maxLength":1000,"type":"string"},"media_ids":{"items":{"type":"string"},"maxItems":12,"type":"array"},"music":{"description":"none = « Sans musique »: the user adds a trending licensed sound in TikTok / Instagram; the rushes' sound is kept","enum":["auto","pulse","chill","drive","none"],"type":"string"},"recipe":{"type":"object"},"reference_url":{"type":"string"},"rights":{"type":"boolean"},"template_id":{"type":"string"}},"required":[],"type":"object"},"name":"montage_create","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Montage create"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Montage get"},"description":"Montage: current state of a montage (status queued|running|rendering|done|failed, stage, recipe, plan, report, url when done, edit_url).","inputSchema":{"additionalProperties":false,"properties":{"montage_id":{"type":"string"}},"required":["montage_id"],"type":"object"},"name":"montage_get","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Montage get"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Montage wait"},"description":"Montage: wait until the montage is done or failed (polls), then return it.","inputSchema":{"additionalProperties":false,"properties":{"montage_id":{"type":"string"},"timeout_s":{"description":"120 s max without a progress stream; call again if still running","maximum":1800,"minimum":5,"type":"integer"}},"required":["montage_id"],"type":"object"},"name":"montage_wait","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Montage wait"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Montage pro"},"description":"Montage pro (paid plans): a short edited like the hand-made ads — Premium render plus, by recipe: cold open on the strongest verified line said in the clip, TikTok loop with rewind (last frame meets the first; the loop already opens on the payoff, so no separate cold open), a musical beat (a 1.5-3.5 s moment WITHOUT speech from the source, chosen by loudness/motion, real sound + music), an interlude shot from the video's shot index relevant to what is said, the clip title in white boxes, split screen (speaker on top, the video's own shots below, captions on the seam), animated data cards taken from what is said (counter, table, transfer form, quote; every number checked against the transcript), ambient shots under the voice, bold uppercase captions with the keyword in orange, orange progress bar, rhythm (ums/silences cut light|normal|aggressive), hook check (score 0-3 in export.montage_report). Recipes: entrepreneur = business (cold open, split screen, data cards), podcasteur = story (title, ambient shots, musical beat, loop), formateur, ecole, marque. Natural-language requests map to arguments: « façon pub » = recipe marque, « podcast / story » = podcasteur, « formation » = formateur, « cours / école » = ecole, otherwise entrepreneur; « avec une boucle » = loop:true; « sans musique » = music:false (no music at all; the musical beat keeps its real sound). Or pass the user's words in `brief` with recipe auto. clip_id = one short (returns the export); video_id = the n best moments of the video (returns {exports}). Then wait_export; the export's montage_report says what was applied or skipped. No extra AI cost beyond the Premium render.","inputSchema":{"additionalProperties":false,"properties":{"brief":{"description":"With recipe auto: the user's request in their words","maxLength":300,"type":"string"},"clip_id":{"description":"Clip id (UUID)","type":"string"},"cold_open":{"type":"boolean"},"intermede":{"type":"boolean"},"loop":{"type":"boolean"},"moment":{"type":"boolean"},"music":{"description":"false = « Sans musique » : no music added by ClipOnIt (the clip's own sound/voice is kept, loudness unchanged); the user then adds a trending licensed sound in TikTok / Instagram. Remembered with the clip edit.","type":"boolean"},"n":{"default":3,"description":"With video_id: how many shorts (best scores first)","maximum":20,"minimum":1,"type":"integer"},"orientation":{"default":"vertical","description":"Target orientation of the edit/export: vertical (9:16, 1080x1920), horizontal (16:9), square (1:1, 1080x1080), portrait45 (4:5, 1080x1350). Each orientation has its own independent edit.","enum":["vertical","horizontal","square","portrait45"],"type":"string"},"recipe":{"default":"auto","enum":["auto","entrepreneur","podcasteur","formateur","ecole","marque"],"type":"string"},"title":{"type":"boolean"},"video_id":{"description":"Video id (UUID)","type":"string"}},"required":[],"type":"object"},"name":"montage_pro","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Montage pro"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Export all clips"},"description":"Render every clip of a video for the orientation (one export per clip, same rules as export_clip; the subtitle preset applies to untouched clips). Returns {exports:[{clip_id, export_id}]}; then wait_export on each id (exports render one at a time).","inputSchema":{"additionalProperties":false,"properties":{"music":{"description":"false = « Sans musique » : no music added by ClipOnIt (the clip's own sound/voice is kept, loudness unchanged); the user then adds a trending licensed sound in TikTok / Instagram. Remembered with the clip edit.","type":"boolean"},"orientation":{"default":"vertical","description":"Target orientation of the edit/export: vertical (9:16, 1080x1920), horizontal (16:9), square (1:1, 1080x1080), portrait45 (4:5, 1080x1350). Each orientation has its own independent edit.","enum":["vertical","horizontal","square","portrait45"],"type":"string"},"style":{"default":"standard","description":"standard = the edit as is. premium (paid plans) = edited for the scroll at render time, the edit is not modified: white hook card on the first 3 s quoting a sentence actually said in the clip (else the clip title), big uppercase 3-word captions with keywords in yellow, ums and silences cut, illustration shots taken from the video, light punch-ins on keywords, no caption when nobody speaks, audio at -14 LUFS.","enum":["standard","premium"],"type":"string"},"tics":{"description":"Also remove ums, hesitations, repeats and dead air just before rendering (same as remove_filler_words apply:true; manual cuts kept).","enum":["light","normal","aggressive"],"type":"string"},"video_id":{"description":"Video id (UUID)","type":"string"}},"required":["video_id"],"type":"object"},"name":"export_all_clips","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Export all clips"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Get export"},"description":"Get an export's status (queued|running|done|failed|canceled), progress (0-100 %), eta_s (estimated seconds left while queued/running), error, and its url (/files/exports/<id>.mp4, relative to the server) when done. Prefer wait_export.","inputSchema":{"additionalProperties":false,"properties":{"export_id":{"description":"Export id returned by export_clip / export_all_clips","type":"string"}},"required":["export_id"],"type":"object"},"name":"get_export","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Get export"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Wait export"},"description":"Block (server-side, up to timeout_s ≤ 120) until the export is done, failed or canceled, then return it like get_export (url when done). If still queued/running at the timeout, returns it with timed_out:true, progress and eta_s: call again.","inputSchema":{"additionalProperties":false,"properties":{"export_id":{"description":"Export id returned by export_clip / export_all_clips","type":"string"},"timeout_s":{"default":120.0,"description":"Max seconds to block (server-side long-poll); the object is returned with timed_out:true if still in progress","maximum":120.0,"minimum":1,"type":"number"}},"required":["export_id"],"type":"object"},"name":"wait_export","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Wait export"},{"annotations":{"destructiveHint":true,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":false,"title":"Cancel export"},"description":"Cancel a queued or running export (the ffmpeg render is killed). Returns the export with status canceled; 400 if it already finished.","inputSchema":{"additionalProperties":false,"properties":{"export_id":{"description":"Export id returned by export_clip / export_all_clips","type":"string"}},"required":["export_id"],"type":"object"},"name":"cancel_export","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Cancel export"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Get export frame"},"description":"Grab one frame of a finished export at t_s seconds (from the start of the rendered clip) as a JPEG (longest side 360 px) to check framing and subtitles. Returns {url, bytes} plus the image inline (base64) when under 200 KB.","inputSchema":{"additionalProperties":false,"properties":{"export_id":{"description":"Export id returned by export_clip / export_all_clips","type":"string"},"t_s":{"default":0,"description":"Seconds from the start of the export","minimum":0,"type":"number"}},"required":["export_id"],"type":"object"},"name":"get_export_frame","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Get export frame"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Get transcript"},"description":"Get the clip's transcript segments [{start, end, text, words:[{start,end,word}]|null}] in absolute source seconds.","inputSchema":{"additionalProperties":false,"properties":{"clip_id":{"description":"Clip id (UUID)","type":"string"}},"required":["clip_id"],"type":"object"},"name":"get_transcript","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Get transcript"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Get quota"},"description":"Get the user's plan and quota: {plan, videos_used, videos_limit, max_bytes, max_video_s (seconds|null), retention_days (days|null = never deleted), searches_today, searches_limit|null, watermark, logo, premiere_xml, edit_captions, translate, broll_music, api_mcp, explainers_used, explainers_limit (explainer renders this month), assist_today/assist_limit, critique_today/critique_limit, forge_today/forge_limit (daily AI requests), audio_min_used, audio_min_limit (voice-over minutes this month), source_min_used, source_min_limit (minutes of source video analyzed by the clipper this month; a video longer than what is left is refused)}. Call it first, before anything that counts against a quota (import_video_url, upload_video_file, explainer_render, explainer_produce, AI tools).","inputSchema":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"name":"get_quota","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Get quota"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Explainer styles"},"description":"List the explainer video styles: [{id, name, description, family (visual family), scene ('2d' = scenes shaped like pixel-news, '3d' = like archibald), tags, poster, theme, scene_fields, video_fields, camera_presets, catalogue, library, safe_area}]. safe_area = usable area in panel coordinates (pixel-news e.g. {x:[0,180], y:[-18,150]}: subtitles above, caption/credit below): keep every element's box inside it. Pick one, then explainer_library {style}.","inputSchema":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"name":"explainer_styles","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer styles"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Explainer library"},"description":"Read one style's explainer catalogue for an AI: common fields, animations, camera presets, transitions, then an INDEX of element names by family (~6k tokens). The library holds ~10,000 elements filed in a taxonomy; this index only lists the general-purpose set, reach the rest with explainer_suggest_elements, explainer_browse or explainer_search_elements. Recommended order: explainer_library ONCE per conversation and style -> explainer_suggest_elements for each scene (or explainer_browse) -> explainer_describe_elements for the names you keep (exact parameters and recolorable parts) -> write the script referencing elements by name (never draw or describe pixels) -> explainer_check -> explainer_render. Animated previews: /explainer-assets/<style>/el/<name>.webp.","inputSchema":{"additionalProperties":false,"properties":{"style":{"default":"pixel-news","description":"Video style id from explainer_styles (pixel-news, archibald, …)","type":"string"}},"required":[],"type":"object"},"name":"explainer_library","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer library"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Explainer suggest elements"},"description":"Give one scene's narration sentence (`text`), or up to 60 at once (`texts`, one per scene or per concept: ONE call for the whole video, returns {results:[one text per input, same order]}), get the most relevant explainer elements by MEANING (semantic embeddings + keywords: synonyms and paraphrases work, one result per concept of the sentence; lines with name, path, tags, preview). Short concept queries ('gaz lacrymogène', 'checkpoint') beat whole sentences. Then explainer_describe_elements on the names you keep. Without style: all styles.","inputSchema":{"additionalProperties":false,"properties":{"limit":{"default":10,"maximum":30,"minimum":1,"type":"integer"},"style":{"description":"Video style id from explainer_styles; omit for pixel-news + archibald","type":"string"},"text":{"description":"Narration sentence of the scene (French or English)","maxLength":500,"type":"string"},"texts":{"description":"Several sentences / concept queries in one call (instead of text)","items":{"maxLength":500,"type":"string"},"maxItems":60,"minItems":1,"type":"array"}},"required":[],"type":"object"},"name":"explainer_suggest_elements","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer suggest elements"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Explainer browse"},"description":"Browse the explainer element taxonomy (~10,000 elements): call it without path to get the 48 root categories with counts, then descend with path (e.g. '01_GEOGRAPHIE/Pays'). A node returns its sub-nodes, plus its elements when it holds 80 or fewer. Without style: all styles.","inputSchema":{"additionalProperties":false,"properties":{"path":{"description":"Taxonomy path, '/'-separated; omit for the root categories","maxLength":200,"type":"string"},"style":{"description":"Video style id from explainer_styles; omit for pixel-news + archibald","type":"string"}},"required":[],"type":"object"},"name":"explainer_browse","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer browse"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Explainer describe elements"},"description":"Exact parameters (with defaults), description and recolorable parts of the given explainer elements. Use it for every element you put in a script (only the parameters returned here exist).","inputSchema":{"additionalProperties":false,"properties":{"names":{"description":"Element names from explainer_library, explainer_suggest_elements, explainer_browse or explainer_search_elements","items":{"type":"string"},"maxItems":60,"type":"array"},"style":{"default":"pixel-news","description":"Video style id from explainer_styles (pixel-news, archibald, …)","type":"string"}},"required":["names"],"type":"object"},"name":"explainer_describe_elements","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer describe elements"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Explainer search elements"},"description":"Keyword search in a style's explainer element library (both styles, French or English, accents ignored), e.g. 'hôpital', 'argent banque', 'vote'. Returns the full lines of the best matches, each with ` | path=… | tags=… | preview=…`. For a whole narration sentence prefer explainer_suggest_elements.","inputSchema":{"additionalProperties":false,"properties":{"limit":{"default":25,"maximum":60,"minimum":1,"type":"integer"},"query":{"type":"string"},"style":{"default":"pixel-news","description":"Video style id from explainer_styles (pixel-news, archibald, …)","type":"string"}},"required":["query"],"type":"object"},"name":"explainer_search_elements","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer search elements"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Explainer check"},"description":"Validate an explainer script against the catalogue. Returns {errors:[...], warnings:[...]}: errors (empty = valid) block the render, fix and re-check; warnings do not block (e.g. 'scène 3, élément 2 (label) : touche la zone des sous-titres' = the element's box enters the subtitles / caption area, see safe_area in explainer_styles; a word anchor not found in `say` is an error) but fix them unless deliberate.","inputSchema":{"additionalProperties":false,"properties":{"script":{"description":"Explainer script. style 'pixel-news' (default): {title, box, theme?, voice:{model, voice, instructions}, scenes:[{say, date, credit, stamp?, elements:[{el, x, y, in, anim, loop, out, exit, k, color?, tint?, parts?, who?, scale?, anchor?, zoom?, ...element params}], camera?:{target: element index, ...}}], cast?, gap?, subscribe_text?} (panel 180x130 px). Word timing: `in`/`out` take seconds OR {word, occurrence?=1, offset?=0, edge?:'end'} = when that word of the scene's `say` is spoken (accents/case/punctuation ignored; resolved from the real voice timing at render). cast: {id: {hair, hair_color, beard, glasses, outfit, colors, party, label...}} and element `who`: id merges those look params under the element's own (element `figure` = configurable person). scale 0.25..6 (default 1), anchor center|bottom; zoom {to, at:[t0,t1], x?, y?} animates one element's zoom. gap = silence between scenes (s, default 0.3, 0..2; always 0 with an imported audio). subscribe_text = end-screen slogan ('' = none). style 'archibald': {style:'archibald', title, theme?, voice, scenes:[{say, text?, big?, tag?, tag_style?, camera?: preset|{move, target, dist, azimuth, elevation, fov, amount, ease, shake}|{keys}, elements:[{el, x, z, y, rot, scale, in, anim, highlight?, to?, move?, ...}], overlays:[{el, x, y | at:[x,y,z], in, ...}]}]} (3D in meters). Other styles (explainer_styles): {style:'<id>', ...} with that style's scene_fields. See explainer_library {style}.","type":"object"}},"required":["script"],"type":"object"},"name":"explainer_check","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer check"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Explainer storyboard"},"description":"Render a storyboard of ALL scenes of an explainer script in one JPEG (inline base64): one numbered thumbnail per scene at `at` (0..1) of each scene's duration. Use it to review a whole script at once before explainer_render. ~0.2 s per scene.","inputSchema":{"additionalProperties":false,"properties":{"at":{"default":0.6,"maximum":1,"minimum":0,"type":"number"},"cols":{"default":4,"maximum":8,"minimum":1,"type":"integer"},"script":{"description":"Explainer script. style 'pixel-news' (default): {title, box, theme?, voice:{model, voice, instructions}, scenes:[{say, date, credit, stamp?, elements:[{el, x, y, in, anim, loop, out, exit, k, color?, tint?, parts?, who?, scale?, anchor?, zoom?, ...element params}], camera?:{target: element index, ...}}], cast?, gap?, subscribe_text?} (panel 180x130 px). Word timing: `in`/`out` take seconds OR {word, occurrence?=1, offset?=0, edge?:'end'} = when that word of the scene's `say` is spoken (accents/case/punctuation ignored; resolved from the real voice timing at render). cast: {id: {hair, hair_color, beard, glasses, outfit, colors, party, label...}} and element `who`: id merges those look params under the element's own (element `figure` = configurable person). scale 0.25..6 (default 1), anchor center|bottom; zoom {to, at:[t0,t1], x?, y?} animates one element's zoom. gap = silence between scenes (s, default 0.3, 0..2; always 0 with an imported audio). subscribe_text = end-screen slogan ('' = none). style 'archibald': {style:'archibald', title, theme?, voice, scenes:[{say, text?, big?, tag?, tag_style?, camera?: preset|{move, target, dist, azimuth, elevation, fov, amount, ease, shake}|{keys}, elements:[{el, x, z, y, rot, scale, in, anim, highlight?, to?, move?, ...}], overlays:[{el, x, y | at:[x,y,z], in, ...}]}]} (3D in meters). Other styles (explainer_styles): {style:'<id>', ...} with that style's scene_fields. See explainer_library {style}.","type":"object"}},"required":["script"],"type":"object"},"name":"explainer_storyboard","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer storyboard"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Explainer templates"},"description":"Ready-made explainer scripts (examples of the style, good starting points). Without `id`: list [{id, style, title, scenes, words, duration}]. With `id` (\"<style>/<name>\"): the full script, to adapt and render.","inputSchema":{"additionalProperties":false,"properties":{"id":{"description":"Template id from the list, e.g. pixel-news/script","type":"string"}},"required":[],"type":"object"},"name":"explainer_templates","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer templates"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Explainer preview"},"description":"Render one still frame (1080x1920 JPEG, inline base64) of scene `scene` (index from 0) at `t` seconds from the scene start, to check a composition. No voice: word timings are estimated. ~1 s.","inputSchema":{"additionalProperties":false,"properties":{"scene":{"default":0,"minimum":0,"type":"integer"},"script":{"description":"Explainer script. style 'pixel-news' (default): {title, box, theme?, voice:{model, voice, instructions}, scenes:[{say, date, credit, stamp?, elements:[{el, x, y, in, anim, loop, out, exit, k, color?, tint?, parts?, who?, scale?, anchor?, zoom?, ...element params}], camera?:{target: element index, ...}}], cast?, gap?, subscribe_text?} (panel 180x130 px). Word timing: `in`/`out` take seconds OR {word, occurrence?=1, offset?=0, edge?:'end'} = when that word of the scene's `say` is spoken (accents/case/punctuation ignored; resolved from the real voice timing at render). cast: {id: {hair, hair_color, beard, glasses, outfit, colors, party, label...}} and element `who`: id merges those look params under the element's own (element `figure` = configurable person). scale 0.25..6 (default 1), anchor center|bottom; zoom {to, at:[t0,t1], x?, y?} animates one element's zoom. gap = silence between scenes (s, default 0.3, 0..2; always 0 with an imported audio). subscribe_text = end-screen slogan ('' = none). style 'archibald': {style:'archibald', title, theme?, voice, scenes:[{say, text?, big?, tag?, tag_style?, camera?: preset|{move, target, dist, azimuth, elevation, fov, amount, ease, shake}|{keys}, elements:[{el, x, z, y, rot, scale, in, anim, highlight?, to?, move?, ...}], overlays:[{el, x, y | at:[x,y,z], in, ...}]}]} (3D in meters). Other styles (explainer_styles): {style:'<id>', ...} with that style's scene_fields. See explainer_library {style}.","type":"object"},"t":{"default":0,"description":"Seconds from the start of the scene","minimum":0,"type":"number"}},"required":["script"],"type":"object"},"name":"explainer_preview","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer preview"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Explainer compose"},"description":"Let the server's Claude write a complete, validated explainer script about a topic (up to ~3 min). Returns {script}; review/preview it, then explainer_render. Uses the monthly AI budget (HTTP 402 when spent) and one AI request at a time per account (429). For a finished MP4 in one call use explainer_produce instead. Paid plans.","inputSchema":{"additionalProperties":false,"properties":{"selection":{"description":"Optional subset of the catalogue to restrict the elements used","type":"object"},"style":{"default":"pixel-news","description":"Video style id from explainer_styles (pixel-news, archibald, …)","type":"string"},"topic":{"description":"What the video explains, e.g. 'the year 2038 bug'","type":"string"}},"required":["topic"],"type":"object"},"name":"explainer_compose","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer compose"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Explainer assist"},"description":"AI director: let the server's Claude modify an explainer script from a natural-language instruction (e.g. 'make scene 3 more dramatic', 'replace the robot with a human', 'speed up the pace', 'check the overlaps'). It searches the element library and validates with explainer_check. Returns {summary, ops:[{op, path, value?, from?, why}] (RFC 6902 JSON Patch on the script, one sentence `why` each; may be [] when it only answers), script (= the input with the ops applied, 0 errors), warnings, usage:{input_tokens, output_tokens, cost_usd}, remaining (requests left today|null)}. Show the ops to the user before rendering. Up to ~4 min; one request at a time per account (429), daily quota per plan (402). Paid plans.","inputSchema":{"additionalProperties":false,"properties":{"history":{"description":"Previous turns of the conversation (last 12 kept)","items":{"properties":{"role":{"enum":["user","assistant"],"type":"string"},"text":{"maxLength":2000,"type":"string"}},"type":"object"},"maxItems":12,"type":"array"},"lang":{"default":"fr","description":"Language of summary and why","enum":["fr","en"],"type":"string"},"message":{"description":"What to change, in plain language","maxLength":2000,"minLength":1,"type":"string"},"scene":{"description":"Scene index the user is looking at (from 0)","minimum":0,"type":"integer"},"script":{"description":"Explainer script. style 'pixel-news' (default): {title, box, theme?, voice:{model, voice, instructions}, scenes:[{say, date, credit, stamp?, elements:[{el, x, y, in, anim, loop, out, exit, k, color?, tint?, parts?, who?, scale?, anchor?, zoom?, ...element params}], camera?:{target: element index, ...}}], cast?, gap?, subscribe_text?} (panel 180x130 px). Word timing: `in`/`out` take seconds OR {word, occurrence?=1, offset?=0, edge?:'end'} = when that word of the scene's `say` is spoken (accents/case/punctuation ignored; resolved from the real voice timing at render). cast: {id: {hair, hair_color, beard, glasses, outfit, colors, party, label...}} and element `who`: id merges those look params under the element's own (element `figure` = configurable person). scale 0.25..6 (default 1), anchor center|bottom; zoom {to, at:[t0,t1], x?, y?} animates one element's zoom. gap = silence between scenes (s, default 0.3, 0..2; always 0 with an imported audio). subscribe_text = end-screen slogan ('' = none). style 'archibald': {style:'archibald', title, theme?, voice, scenes:[{say, text?, big?, tag?, tag_style?, camera?: preset|{move, target, dist, azimuth, elevation, fov, amount, ease, shake}|{keys}, elements:[{el, x, z, y, rot, scale, in, anim, highlight?, to?, move?, ...}], overlays:[{el, x, y | at:[x,y,z], in, ...}]}]} (3D in meters). Other styles (explainer_styles): {style:'<id>', ...} with that style's scene_fields. See explainer_library {style}.","type":"object"},"selection":{"description":"Selected element of that scene","properties":{"index":{"minimum":0,"type":"integer"},"layer":{"enum":["elements","overlays"],"type":"string"}},"type":"object"}},"required":["script","message"],"type":"object"},"name":"explainer_assist","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer assist"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Explainer variants"},"description":"3 variants of one scene: the server's Claude proposes 3 truly different versions of scene `scene` (staging, elements, rhythm; narration `say` kept as is, each one passes explainer_check), costs about 0.9 $ on the monthly AI budget. Returns {variants:[{label, why, scene, axes:[staging|elements|rhythm], dur}], prefs_used, warnings, forged, usage:{input_tokens, output_tokens, cost_usd}, remaining}. Let the user pick one, put its `scene` at script.scenes[scene], then explainer_check / explainer_render. Up to ~4 min; one AI request at a time per account (429), daily quota and monthly AI budget (402). Paid plans.","inputSchema":{"additionalProperties":false,"properties":{"brief":{"description":"Optional hint, e.g. 'calmer', 'more data'","maxLength":500,"type":"string"},"lang":{"default":"fr","description":"Language of label and why","enum":["fr","en"],"type":"string"},"scene":{"description":"Index of the scene to vary (from 0)","minimum":0,"type":"integer"},"script":{"description":"Explainer script. style 'pixel-news' (default): {title, box, theme?, voice:{model, voice, instructions}, scenes:[{say, date, credit, stamp?, elements:[{el, x, y, in, anim, loop, out, exit, k, color?, tint?, parts?, who?, scale?, anchor?, zoom?, ...element params}], camera?:{target: element index, ...}}], cast?, gap?, subscribe_text?} (panel 180x130 px). Word timing: `in`/`out` take seconds OR {word, occurrence?=1, offset?=0, edge?:'end'} = when that word of the scene's `say` is spoken (accents/case/punctuation ignored; resolved from the real voice timing at render). cast: {id: {hair, hair_color, beard, glasses, outfit, colors, party, label...}} and element `who`: id merges those look params under the element's own (element `figure` = configurable person). scale 0.25..6 (default 1), anchor center|bottom; zoom {to, at:[t0,t1], x?, y?} animates one element's zoom. gap = silence between scenes (s, default 0.3, 0..2; always 0 with an imported audio). subscribe_text = end-screen slogan ('' = none). style 'archibald': {style:'archibald', title, theme?, voice, scenes:[{say, text?, big?, tag?, tag_style?, camera?: preset|{move, target, dist, azimuth, elevation, fov, amount, ease, shake}|{keys}, elements:[{el, x, z, y, rot, scale, in, anim, highlight?, to?, move?, ...}], overlays:[{el, x, y | at:[x,y,z], in, ...}]}]} (3D in meters). Other styles (explainer_styles): {style:'<id>', ...} with that style's scene_fields. See explainer_library {style}.","type":"object"}},"required":["script","scene"],"type":"object"},"name":"explainer_variants","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer variants"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Explainer critique"},"description":"AI review: renders 1 to 3 key frames per scene and has the server's Claude (vision) score each scene 0-100 (readability, overlaps, empty space, match with the narration, visual hierarchy, subtitles and safe areas), plus free deterministic checks. Applies nothing on its own: ops are the fixes it proposes (one pass, not re-checked) and script = the input with those ops applied, so it differs from the input whenever ops is non-empty; keep using the input script unless the user accepts the ops. Returns {summary, scores:[{scene, score 0..100, issues:[{kind, severity: high|medium|low, element?:{layer, index}, other?, t? (s from scene start), why, source: lint|vision}]}] (input script), after (same for the returned script | null), ops:[{op, path, value?, from?, why}] (RFC 6902 JSON Patch), script, warnings, passes, usage:{input_tokens, output_tokens, cost_usd}, remaining (runs left today|null)}. Up to ~7 min; one AI request (assist or critique) at a time per account (429), daily quota per plan and shared monthly AI budget (402). Paid plans.","inputSchema":{"additionalProperties":false,"properties":{"lang":{"default":"fr","description":"Language of summary and why","enum":["fr","en"],"type":"string"},"scene":{"description":"Only this scene (index from 0); omit for the whole video","minimum":0,"type":"integer"},"script":{"description":"Explainer script. style 'pixel-news' (default): {title, box, theme?, voice:{model, voice, instructions}, scenes:[{say, date, credit, stamp?, elements:[{el, x, y, in, anim, loop, out, exit, k, color?, tint?, parts?, who?, scale?, anchor?, zoom?, ...element params}], camera?:{target: element index, ...}}], cast?, gap?, subscribe_text?} (panel 180x130 px). Word timing: `in`/`out` take seconds OR {word, occurrence?=1, offset?=0, edge?:'end'} = when that word of the scene's `say` is spoken (accents/case/punctuation ignored; resolved from the real voice timing at render). cast: {id: {hair, hair_color, beard, glasses, outfit, colors, party, label...}} and element `who`: id merges those look params under the element's own (element `figure` = configurable person). scale 0.25..6 (default 1), anchor center|bottom; zoom {to, at:[t0,t1], x?, y?} animates one element's zoom. gap = silence between scenes (s, default 0.3, 0..2; always 0 with an imported audio). subscribe_text = end-screen slogan ('' = none). style 'archibald': {style:'archibald', title, theme?, voice, scenes:[{say, text?, big?, tag?, tag_style?, camera?: preset|{move, target, dist, azimuth, elevation, fov, amount, ease, shake}|{keys}, elements:[{el, x, z, y, rot, scale, in, anim, highlight?, to?, move?, ...}], overlays:[{el, x, y | at:[x,y,z], in, ...}]}]} (3D in meters). Other styles (explainer_styles): {style:'<id>', ...} with that style's scene_fields. See explainer_library {style}.","type":"object"}},"required":["script"],"type":"object"},"name":"explainer_critique","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer critique"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Explainer polish"},"description":"AI polish: explainer_critique, then fixes the issues it found (JSON Patch ops, one sentence `why` each; no narration change, no scene added or removed; restricted to `scene` if given) and re-scores, 2 passes max, stopping when the score no longer rises. Returns {summary, scores:[{scene, score 0..100, issues:[{kind, severity: high|medium|low, element?:{layer, index}, other?, t? (s from scene start), why, source: lint|vision}]}] (input script), after (same for the returned script | null), ops:[{op, path, value?, from?, why}] (RFC 6902 JSON Patch), script, warnings, passes, usage:{input_tokens, output_tokens, cost_usd}, remaining (runs left today|null)}. Show the ops to the user before rendering; the input script stays untouched until they accept. Up to ~7 min; one AI request (assist or critique) at a time per account (429), daily quota per plan and shared monthly AI budget (402). Paid plans.","inputSchema":{"additionalProperties":false,"properties":{"lang":{"default":"fr","description":"Language of summary and why","enum":["fr","en"],"type":"string"},"scene":{"description":"Only this scene (index from 0); omit for the whole video","minimum":0,"type":"integer"},"script":{"description":"Explainer script. style 'pixel-news' (default): {title, box, theme?, voice:{model, voice, instructions}, scenes:[{say, date, credit, stamp?, elements:[{el, x, y, in, anim, loop, out, exit, k, color?, tint?, parts?, who?, scale?, anchor?, zoom?, ...element params}], camera?:{target: element index, ...}}], cast?, gap?, subscribe_text?} (panel 180x130 px). Word timing: `in`/`out` take seconds OR {word, occurrence?=1, offset?=0, edge?:'end'} = when that word of the scene's `say` is spoken (accents/case/punctuation ignored; resolved from the real voice timing at render). cast: {id: {hair, hair_color, beard, glasses, outfit, colors, party, label...}} and element `who`: id merges those look params under the element's own (element `figure` = configurable person). scale 0.25..6 (default 1), anchor center|bottom; zoom {to, at:[t0,t1], x?, y?} animates one element's zoom. gap = silence between scenes (s, default 0.3, 0..2; always 0 with an imported audio). subscribe_text = end-screen slogan ('' = none). style 'archibald': {style:'archibald', title, theme?, voice, scenes:[{say, text?, big?, tag?, tag_style?, camera?: preset|{move, target, dist, azimuth, elevation, fov, amount, ease, shake}|{keys}, elements:[{el, x, z, y, rot, scale, in, anim, highlight?, to?, move?, ...}], overlays:[{el, x, y | at:[x,y,z], in, ...}]}]} (3D in meters). Other styles (explainer_styles): {style:'<id>', ...} with that style's scene_fields. See explainer_library {style}.","type":"object"}},"required":["script"],"type":"object"},"name":"explainer_polish","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer polish"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Explainer render"},"description":"Render an explainer video from a script (voice-over TTS, word timing, pixel-art scenes, ~4 min). Validated first: HTTP 400 lists the errors; warnings never block and come back in the response. Returns {id, status:'queued', warnings}; then explainer_wait. Counts against the monthly explainer renders (get_quota explainers_used / explainers_limit: 30 Starter, 100 Pro, 400 Business; HTTP 402 when reached). Paid plans.","inputSchema":{"additionalProperties":false,"properties":{"script":{"description":"Explainer script. style 'pixel-news' (default): {title, box, theme?, voice:{model, voice, instructions}, scenes:[{say, date, credit, stamp?, elements:[{el, x, y, in, anim, loop, out, exit, k, color?, tint?, parts?, who?, scale?, anchor?, zoom?, ...element params}], camera?:{target: element index, ...}}], cast?, gap?, subscribe_text?} (panel 180x130 px). Word timing: `in`/`out` take seconds OR {word, occurrence?=1, offset?=0, edge?:'end'} = when that word of the scene's `say` is spoken (accents/case/punctuation ignored; resolved from the real voice timing at render). cast: {id: {hair, hair_color, beard, glasses, outfit, colors, party, label...}} and element `who`: id merges those look params under the element's own (element `figure` = configurable person). scale 0.25..6 (default 1), anchor center|bottom; zoom {to, at:[t0,t1], x?, y?} animates one element's zoom. gap = silence between scenes (s, default 0.3, 0..2; always 0 with an imported audio). subscribe_text = end-screen slogan ('' = none). style 'archibald': {style:'archibald', title, theme?, voice, scenes:[{say, text?, big?, tag?, tag_style?, camera?: preset|{move, target, dist, azimuth, elevation, fov, amount, ease, shake}|{keys}, elements:[{el, x, z, y, rot, scale, in, anim, highlight?, to?, move?, ...}], overlays:[{el, x, y | at:[x,y,z], in, ...}]}]} (3D in meters). Other styles (explainer_styles): {style:'<id>', ...} with that style's scene_fields. See explainer_library {style}.","type":"object"}},"required":["script"],"type":"object"},"name":"explainer_render","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer render"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Explainer wait"},"description":"Block (server-side, up to timeout_s ≤ 120) until the explainer video is done or failed, then return {id, title, status, progress (0..1), url (/files/exports/explainer_<id>.mp4 when done), error, script}. If still queued/running at the timeout, returns it with timed_out:true: call again.","inputSchema":{"additionalProperties":false,"properties":{"id":{"description":"Explainer id returned by explainer_render","type":"string"},"timeout_s":{"default":120.0,"description":"Max seconds to block (server-side long-poll); the object is returned with timed_out:true if still in progress","maximum":120.0,"minimum":1,"type":"number"}},"required":["id"],"type":"object"},"name":"explainer_wait","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer wait"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":true,"readOnlyHint":false,"title":"Explainer import audio"},"description":"Import the user's OWN voice-over for an explainer video from a link (YouTube or a direct audio/video URL; audio only, max 30 min). The server downloads it, transcribes it word by word and splits it into scenes. Returns {id, status:'queued'}. Flow: explainer_import_audio -> explainer_audio_status {id} until 'done' -> explainer_compose_from_audio {id} -> explainer_check -> explainer_render (the video then plays this audio instead of a TTS voice). A file upload goes through the API (POST /api/explainer/audio, multipart `file`) or the editor. If YouTube blocks the download, ask the user to upload the file. Paid plans.","inputSchema":{"additionalProperties":false,"properties":{"url":{"description":"https:// link: YouTube video or direct audio/video file","type":"string"}},"required":["url"],"type":"object"},"name":"explainer_import_audio","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer import audio"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Explainer audio status"},"description":"Block (server-side, up to timeout_s ≤ 120) until an imported audio is done or failed, then return {id, name, status (queued|running|done|failed), progress 0..1, error, duration (s)} plus, when done, {text (full transcript), language, scenes:[{say, start, end}]}. If still running at the timeout, returns it with timed_out:true: call again.","inputSchema":{"additionalProperties":false,"properties":{"id":{"description":"Audio id returned by explainer_import_audio","type":"string"},"timeout_s":{"default":120.0,"description":"Max seconds to block (server-side long-poll); the object is returned with timed_out:true if still in progress","maximum":120.0,"minimum":1,"type":"number"}},"required":["id"],"type":"object"},"name":"explainer_audio_status","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer audio status"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Explainer compose from audio"},"description":"Let the server's Claude compose the visuals of an explainer video on an imported audio (status 'done'): one scene per audio segment, the narration text (`say`) is the transcript and is kept as is. Returns {script} whose `audio` = {id, segments:[[start, end], ...]}; keep `audio` and the `say` texts unchanged when editing elements, then explainer_check / explainer_preview / explainer_render. Uses the monthly AI budget (HTTP 402 when spent), one AI request at a time per account (429). Paid plans.","inputSchema":{"additionalProperties":false,"properties":{"id":{"description":"Audio id (explainer_import_audio)","type":"string"},"style":{"default":"pixel-news","description":"Video style id from explainer_styles (pixel-news, archibald, …)","type":"string"},"topic":{"description":"Optional hint about the subject, to guide the visuals","type":"string"}},"required":["id"],"type":"object"},"name":"explainer_compose_from_audio","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer compose from audio"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":true,"readOnlyHint":false,"title":"Explainer narrate"},"description":"Voice first: send the user's OWN narration (recorded voice-over) and get it transcribed word by word and split into scenes, ready for explainer_compose_from_audio. Give `audio_base64` (any audio: webm/opus, ogg, mp3, m4a, wav; max 20 MB decoded, 15 min) or `audio_url` (same as explainer_import_audio). Light server-side cleaning with ffmpeg, the untouched original is always kept: `clean` {loudnorm (default true), trim_silence (cuts silences > 1 s), denoise}. Counts against the monthly audio minutes of the plan (HTTP 402 when reached, see get_quota audio_min_used / audio_min_limit). Returns {id, status:'queued'}; then explainer_audio_status {id} until 'done' -> explainer_compose_from_audio {id} -> explainer_check -> explainer_render. Paid plans.","inputSchema":{"additionalProperties":false,"properties":{"audio_base64":{"description":"The audio file, base64 (a data: URL is accepted)","type":"string"},"audio_url":{"description":"https:// link instead of audio_base64","type":"string"},"clean":{"additionalProperties":false,"properties":{"denoise":{"type":"boolean"},"loudnorm":{"type":"boolean"},"trim_silence":{"type":"boolean"}},"type":"object"},"name":{"maxLength":200,"type":"string"}},"required":[],"type":"object"},"name":"explainer_narrate","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer narrate"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Explainer retake"},"description":"Re-record ONE scene's sentence of a narrated explainer (script with `audio`): the new take replaces [start, end] of that scene in the audio (cleaned like the original), the words are re-timed and later scenes shift. Creates a NEW audio id; the old one is untouched (undo = keep the old id). Returns {id, parent_id, segments (new script.audio.segments), scene, say (transcript of the take = the scene's new `say`), delta (s added, may be negative), duration}. Put id and segments in script.audio and say in scenes[scene].say: elements timed on words follow. Take max 60 s; one retake at a time per account (429). Paid plans.","inputSchema":{"additionalProperties":false,"properties":{"audio_base64":{"description":"The new take, base64 (webm/opus, ogg, mp3, m4a, wav)","type":"string"},"id":{"description":"script.audio.id","type":"string"},"scene":{"minimum":0,"type":"integer"},"segments":{"description":"script.audio.segments, unchanged","items":{"items":{"type":"number"},"maxItems":2,"minItems":2,"type":"array"},"type":"array"}},"required":["id","scene","segments","audio_base64"],"type":"object"},"name":"explainer_retake","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer retake"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Explainer idea"},"description":"Dictated idea -> plan: give a rough idea in a few sentences (e.g. the transcript of a 30 s - 2 min voice memo: explainer_narrate then explainer_audio_status text) and the server's Claude turns it into a scene plan and the narration text to read. Nothing is generated or rendered: show it to the user, let them edit it, then record it (explainer_narrate) or explainer_compose {topic}. Paid plans.","inputSchema":{"additionalProperties":false,"properties":{"lang":{"enum":["fr","en"],"type":"string"},"style":{"default":"pixel-news","description":"Video style id from explainer_styles (pixel-news, archibald, …)","type":"string"},"text":{"description":"20 to 4000 characters after trimming","maxLength":4000,"minLength":20,"type":"string"}},"required":["text"],"type":"object"},"name":"explainer_idea","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer idea"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":true,"readOnlyHint":false,"title":"Style import"},"description":"Import your own style: from 3 to 10 references of the user (screenshots of their videos, brand chart, illustrations they like, or one short video; optional logo and one .ttf/.otf font) the server measures the palette, line, flat/gradient shading, grain, pixel look, picks the closest explainer engine (pixel 2D, flat vector, toon, sketch, neon, low-poly 3D, voxel…) with a light vision model, and creates a PRIVATE style `perso-<hex>` usable like the built-in ones (`style` of a script, explainer_compose {style}, element library redrawn on demand). Returns {id, name, base, palette, knobs, description, candidates, demo: [3 JPEG URLs of demo scenes], cost_usd}. Ask the user to confirm they own the rights (consent) and never name or imitate a specific creator. Tune it afterwards with PUT /api/explainer/my-styles/{id} (knobs, palette, regenerate). Paid plans, number of styles capped by plan (HTTP 402).","inputSchema":{"additionalProperties":false,"properties":{"consent":{"description":"true = the user confirmed owning the rights to these references","type":"boolean"},"name":{"description":"Style name (no creator name or @handle)","maxLength":40,"type":"string"},"references":{"description":"3+ images (a video counts as 3), 10 max, plus one optional font","items":{"properties":{"base64":{"description":"File content, base64","type":"string"},"filename":{"description":"Name with extension (png, jpg, webp, mp4, mov, webm, ttf, otf): required with base64","type":"string"},"url":{"description":"http(s) URL of an image, short video or font","type":"string"}},"type":"object"},"maxItems":11,"minItems":1,"type":"array"}},"required":["references","consent"],"type":"object"},"name":"style_import","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Style import"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Style list"},"description":"List the user's imported explainer styles (style_import): [{id, name, base, palette, knobs, description, demo, cost_usd}] + limit of the plan. Use the id as the script `style`.","inputSchema":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"name":"style_list","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Style list"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Explainer list"},"description":"List the user's explainer videos, newest first: [{id, title, status (queued|running|done|failed), progress 0..1, url, error, created_at}].","inputSchema":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"name":"explainer_list","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer list"},{"annotations":{"destructiveHint":true,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":false,"title":"Explainer delete"},"description":"Delete one of the user's explainer videos (the row and its MP4; a running render is not stopped, use explainer_job_cancel for agent jobs). Irreversible. Use it when the user asks to remove a video from explainer_list. Returns {ok:true}; 404 = wrong id or another account's video.","inputSchema":{"additionalProperties":false,"properties":{"id":{"description":"Explainer id from explainer_list / explainer_render","type":"string"}},"required":["id"],"type":"object"},"name":"explainer_delete","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer delete"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Explainer audio list"},"description":"List the user's imported voice-overs (explainer_narrate / explainer_import_audio / editor uploads), newest first: [{id, name, source_url, status (queued|running|done|failed), progress 0..1, error, duration (s), created_at}]. Use it to find an audio_id for explainer_compose_from_audio or explainer_produce {audio_id} instead of importing the same audio again.","inputSchema":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"name":"explainer_audio_list","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer audio list"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Explainer my elements"},"description":"List the user's own elements (drawn in the ClipOnIt element editor or created with explainer_create_element): 2D pixel art [{id, name, family, kind:'2d', size 'WxH', images, parts}] and 3D objects [{id, name, kind:'3d', el:'custom3d', primitives (count)}] (archibald only). With {id}: {id, element} where element is the ready-to-insert entry {el:'custom', name, pixels, palette, frames, fps, anchor}; copy it into scene.elements and add x, y, in, k, anim… (parts {name: #hex} recolors a palette entry). Prefer them over drawing again.","inputSchema":{"additionalProperties":false,"properties":{"id":{"description":"Element id: returns the full ready-to-insert entry","type":"string"}},"required":[],"type":"object"},"name":"explainer_my_elements","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer my elements"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Explainer schema"},"description":"JSON Schema (draft 2020-12) of an explainer script for one style, generated from the live catalogue: every scene field, element (with its own params and defaults), animation, camera, transition and part. Use it for strict structured output or to validate a script with any JSON Schema tool; explainer_check stays the final word. The full schema is large (pixel-news ~1000 elements): pass `elements` to get a short schema restricted to the elements you use.","inputSchema":{"additionalProperties":false,"properties":{"elements":{"description":"Optional element names: restricts the schema (other names are then rejected)","items":{"type":"string"},"type":"array"},"style":{"default":"pixel-news","description":"Video style id from explainer_styles (pixel-news, archibald, …)","type":"string"}},"required":[],"type":"object"},"name":"explainer_schema","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer schema"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Explainer export"},"description":"Export to a file: an archibald 3D object or scene -> glb (glTF binary: one node per part, one material per part color; ANIMATED by default like the video: object moves to/move, entries and exits (scale/visibility), moving parts (blades, custom3d motion) as TRS, deforming parts as morph targets, the scene camera as a glTF camera with its move and shake, red highlight via KHR_animation_pointer + a grey/red mesh swap fallback; animate:false = frozen at t; all_scenes:true = the whole video on one timeline, one animation per scene + 'Vidéo complète'; particle effects smoke/steam/fire/explosion/cloud as one animated unlit sphere per particle with color/opacity via KHR_animation_pointer, emissive fire; particles: lite (default, <=150 per effect at <=15 fps) | full | off) or obj (zip: .obj + .mtl, static by nature: animate/all_scenes refused); a pixel-news element (incl. `custom`) or a 3D object seen in orbit -> png, gif (animated, transparent) or sheet (zip: spritesheet .png + .json frames); a whole script -> explainer (.explainer.json, re-importable in the editor). Give exactly one of script (scene = index for glb/obj), element ({el, ...params} or a saved data object) or id (an explainer_my_elements id). Returns {url (temporary, 24 h), filename, bytes, mime} and the file inline when under 200 KB.","inputSchema":{"additionalProperties":false,"properties":{"all_scenes":{"default":false,"description":"Script -> glb: the whole video on one timeline instead of one scene","type":"boolean"},"animate":{"description":"glb: animated (default) or false = static pose at t. obj is always static (true refused)","type":"boolean"},"element":{"description":"One element: {el, ...params} (e.g. {el:'custom3d', primitives:[...]}, {el:'robot', variant:'b'})","type":"object"},"format":{"enum":["glb","obj","png","gif","sheet","explainer"],"type":"string"},"fps":{"default":30,"description":"glb animation sampling rate","maximum":60,"minimum":1,"type":"integer"},"id":{"description":"Saved element id (explainer_my_elements)","type":"string"},"particles":{"default":"lite","description":"Animated glb: particle effects (smoke, steam, fire, explosion, cloud). lite = at most 150 particles per effect sampled at <= 15 fps; full = all at fps; off = none","enum":["full","lite","off"],"type":"string"},"scale":{"default":4,"description":"Pixel scale of png/gif/sheet","maximum":16,"minimum":1,"type":"integer"},"scene":{"default":0,"description":"Scene index (script -> glb/obj)","minimum":0,"type":"integer"},"script":{"description":"Explainer script. style 'pixel-news' (default): {title, box, theme?, voice:{model, voice, instructions}, scenes:[{say, date, credit, stamp?, elements:[{el, x, y, in, anim, loop, out, exit, k, color?, tint?, parts?, who?, scale?, anchor?, zoom?, ...element params}], camera?:{target: element index, ...}}], cast?, gap?, subscribe_text?} (panel 180x130 px). Word timing: `in`/`out` take seconds OR {word, occurrence?=1, offset?=0, edge?:'end'} = when that word of the scene's `say` is spoken (accents/case/punctuation ignored; resolved from the real voice timing at render). cast: {id: {hair, hair_color, beard, glasses, outfit, colors, party, label...}} and element `who`: id merges those look params under the element's own (element `figure` = configurable person). scale 0.25..6 (default 1), anchor center|bottom; zoom {to, at:[t0,t1], x?, y?} animates one element's zoom. gap = silence between scenes (s, default 0.3, 0..2; always 0 with an imported audio). subscribe_text = end-screen slogan ('' = none). style 'archibald': {style:'archibald', title, theme?, voice, scenes:[{say, text?, big?, tag?, tag_style?, camera?: preset|{move, target, dist, azimuth, elevation, fov, amount, ease, shake}|{keys}, elements:[{el, x, z, y, rot, scale, in, anim, highlight?, to?, move?, ...}], overlays:[{el, x, y | at:[x,y,z], in, ...}]}]} (3D in meters). Other styles (explainer_styles): {style:'<id>', ...} with that style's scene_fields. See explainer_library {style}.","type":"object"},"t":{"default":0,"description":"Seconds: pose of a static export (obj, or glb with animate:false)","minimum":0,"type":"number"}},"required":["format"],"type":"object"},"name":"explainer_export","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer export"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Explainer library elements"},"description":"List the shared element library stored on the server (table library_elements: ~10,000 elements from the code packs + elements added as data by admins, enabled only): {total, items:[{style, name, kind: code|data, family, path, tags, description, preview}]}. Filters: q (words in name/description/tags/path), path (taxonomy prefix, e.g. 17_ANIMAUX/Mammifères), kind, style, limit (≤200, default 50), offset; mine: true = only this account's own elements (forged by explainer_forge, private or proposed). Use the name as `el` in a script of that style.","inputSchema":{"additionalProperties":false,"properties":{"kind":{"enum":["code","data"],"type":"string"},"limit":{"maximum":200,"minimum":1,"type":"integer"},"mine":{"description":"Only my elements (private / pending), e.g. the ones made by explainer_forge","type":"boolean"},"offset":{"minimum":0,"type":"integer"},"path":{"type":"string"},"q":{"type":"string"},"style":{"enum":["pixel-news","archibald"],"type":"string"}},"required":[],"type":"object"},"name":"explainer_library_elements","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer library elements"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Explainer forge"},"description":"Draw a missing element with Claude when explainer_suggest_elements / explainer_search_elements find nothing close enough (e.g. 'a pangolin riding a skateboard'). Claude writes it as DATA (style 2d = pixel-news `custom` pixel art, 3d = archibald `custom3d` low-poly primitives, never code), the server renders it, Claude looks at the render and fixes it (2 corrections max), then it is saved as a PRIVATE element of this account: only this account sees it in search / browse / suggest / explainer_library_elements {mine:true}, and any of its scripts can use it by name as {el: '<name>'} (style pixel-news for 2d, archibald for 3d). Returns {element:{style, name, kind:'data', family, path, tags, description, data, preview (animated webp URL), element:{el}, scope:'private', mine:true, source:'forge'}, summary, review:{ok, score 0..10, notes}, iterations, usage:{input_tokens, output_tokens, cost_usd (≈0.2-0.4 $)}, remaining (forges left today|null)}. Up to ~3 min; one AI request (assist, critique or forge) at a time per account (429); daily forge quota per plan (5 Starter, 15 Pro, 50 Business) and shared monthly AI budget (402). Paid plans (403 on free).","inputSchema":{"additionalProperties":false,"properties":{"lang":{"enum":["fr","en"],"type":"string"},"prompt":{"description":"What to draw, one short sentence (FR or EN)","maxLength":300,"type":"string"},"ref_name":{"description":"Optional existing element to take the look from (proportions, palette)","pattern":"^[a-z][a-z0-9_]{1,59}$","type":"string"},"style":{"description":"2d = pixel art for pixel-news, 3d = low-poly object for archibald","enum":["2d","3d"],"type":"string"}},"required":["prompt","style"],"type":"object"},"name":"explainer_forge","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer forge"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Explainer versions"},"description":"Version history of an explainer script, per document `doc` (8-64 chars A-Z a-z 0-9 _ -, chosen by you; reuse it for the same video): action save {doc, script, kind?=manual, label?} records a version (saved:false when identical to the last one), list {doc?} -> versions newest first with a readable diff summary {fr, en} and changes (without doc: the documents), get {id} -> the script, compare {a, b} -> changes from a to b, restore {id, script?=current script} -> the restored script as a new version (undo = restore the previous one).","inputSchema":{"additionalProperties":false,"properties":{"a":{"description":"Version id (compare: from)","type":"string"},"action":{"enum":["list","get","compare","restore","save"],"type":"string"},"b":{"description":"Version id (compare: to)","type":"string"},"doc":{"pattern":"^[A-Za-z0-9_-]{8,64}$","type":"string"},"id":{"description":"Version id (get, restore)","type":"string"},"kind":{"default":"manual","enum":["generate","assist","polish","import","audio","variant","restore","comment","manual"],"type":"string"},"label":{"description":"One-sentence name of the version (default: the diff summary)","maxLength":140,"type":"string"},"script":{"description":"Explainer script. style 'pixel-news' (default): {title, box, theme?, voice:{model, voice, instructions}, scenes:[{say, date, credit, stamp?, elements:[{el, x, y, in, anim, loop, out, exit, k, color?, tint?, parts?, who?, scale?, anchor?, zoom?, ...element params}], camera?:{target: element index, ...}}], cast?, gap?, subscribe_text?} (panel 180x130 px). Word timing: `in`/`out` take seconds OR {word, occurrence?=1, offset?=0, edge?:'end'} = when that word of the scene's `say` is spoken (accents/case/punctuation ignored; resolved from the real voice timing at render). cast: {id: {hair, hair_color, beard, glasses, outfit, colors, party, label...}} and element `who`: id merges those look params under the element's own (element `figure` = configurable person). scale 0.25..6 (default 1), anchor center|bottom; zoom {to, at:[t0,t1], x?, y?} animates one element's zoom. gap = silence between scenes (s, default 0.3, 0..2; always 0 with an imported audio). subscribe_text = end-screen slogan ('' = none). style 'archibald': {style:'archibald', title, theme?, voice, scenes:[{say, text?, big?, tag?, tag_style?, camera?: preset|{move, target, dist, azimuth, elevation, fov, amount, ease, shake}|{keys}, elements:[{el, x, z, y, rot, scale, in, anim, highlight?, to?, move?, ...}], overlays:[{el, x, y | at:[x,y,z], in, ...}]}]} (3D in meters). Other styles (explainer_styles): {style:'<id>', ...} with that style's scene_fields. See explainer_library {style}.","type":"object"}},"required":["action"],"type":"object"},"name":"explainer_versions","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer versions"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Explainer taste get"},"description":"Read what the AI has learned about this account's taste (from its accepted/refused edits, deleted elements, kept/discarded forges, final videos): {learn (bool), prefs:[{id, label, source, ...}], summary, ...}. These preferences (plus the brand kit) are applied automatically to explainer_compose / assist / critique / polish / forge; responses list the ones used in `prefs_used`. Taste data is never shared between accounts.","inputSchema":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"name":"explainer_taste_get","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer taste get"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Explainer brand get"},"description":"Read this account's brand kit: {enabled, colors:[#rrggbb] (2-5 when enabled), font (Inter|Anton|Silkscreen|VT323|null), tone (sobre|dynamique|humoristique|pedagogique|null), watermark:{on, pos: tl|tr|bl|br, opacity 0.1-1, size 0.06-0.25}, intro/outro:{on, text ≤60 chars, logo, dur 1-4 s}, logo_url, fonts, tones, updated_at}. When enabled, the server applies it to every render and preview (palette of recolorable parts, title font, watermark logo, intro/outro cards); a script can opt out with top-level \"brand\": false.","inputSchema":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"name":"explainer_brand_get","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer brand get"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":false,"title":"Explainer brand set"},"description":"Update this account's brand kit: `kit` holds only the fields to change (merged into the current kit, nested watermark/intro/outro merged too), e.g. {enabled: true, colors: ['#1d4ed8', '#f59e0b'], tone: 'sobre', intro: {on: true, text: 'Labo Bleu'}}. Optional logo_base64 (PNG or JPG, ≤ 2 MB) replaces the account logo first (used as watermark and in intro/outro). Returns the stored kit (same shape as explainer_brand_get). HTTP 400 names the invalid field; 403 on the free plan.","inputSchema":{"additionalProperties":false,"properties":{"kit":{"description":"Fields to change: enabled, colors, font, tone, watermark, intro, outro","type":"object"},"logo_base64":{"description":"PNG or JPG image, base64 (≤ 2 MB decoded)","type":"string"}},"required":[],"type":"object"},"name":"explainer_brand_set","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer brand set"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Explainer library add element"},"description":"ADMINS ONLY (ADMIN_EMAILS, else HTTP 403): add an element to the shared library without a redeploy. data = a `custom` pixel-art spec {pixels, palette, frames?, fps?, anchor?} (style pixel-news) or a `custom3d` spec {kind:'3d', primitives} (style archibald), validated like explainer_create_element. name = identifier used as `el` in scripts ([a-z][a-z0-9_], 2-60 chars, unique per style). It then appears in explainer_library / search / suggest / browse and the catalogue page, with an animated preview. Returns the stored element.","inputSchema":{"additionalProperties":false,"properties":{"data":{"description":"custom {pixels, palette, frames, fps, anchor} or custom3d {kind:'3d', primitives}","type":"object"},"description":{"type":"string"},"enabled":{"description":"default true","type":"boolean"},"family":{"maxLength":40,"type":"string"},"name":{"pattern":"^[a-z][a-z0-9_]{1,59}$","type":"string"},"path":{"description":"Taxonomy path, e.g. 17_ANIMAUX/Mammifères/Félins","type":"string"},"style":{"enum":["pixel-news","archibald"],"type":"string"},"tags":{"items":{"type":"string"},"type":"array"}},"required":["name","data"],"type":"object"},"name":"explainer_library_add_element","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer library add element"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Explainer create element"},"description":"Save a new pixel-art element in the user's account (style pixel-news, element `custom`) and return {id, element (ready to insert in a scene), preview (animated webp URL)}. Only when nothing in explainer_library fits. Validated: HTTP 400 lists the errors. Max 128x100 px, 24 colors, 12 images, 200 elements per account. With kind:'3d' it saves an archibald 3D object instead (element `custom3d`: pass primitives, not pixels/palette; max 64 primitives, 16 parts).","inputSchema":{"additionalProperties":false,"properties":{"anchor":{"default":"center","description":"Meaning of x, y: center, or bottom-center for things standing on the ground","enum":["center","bottom"],"type":"string"},"family":{"description":"Optional group name, e.g. 'Animaux'","type":"string"},"fps":{"default":4,"maximum":30,"minimum":0.5,"type":"number"},"frames":{"description":"Optional extra images (same size as pixels) for a frame-by-frame animation","items":{"items":{"type":"string"},"type":"array"},"type":"array"},"kind":{"default":"2d","description":"2d = pixel art (pixels + palette), 3d = custom3d object (primitives)","enum":["2d","3d"],"type":"string"},"name":{"description":"Element name (also its label in the editor)","type":"string"},"palette":{"description":"{char: {name, color}}: name = recolorable part, color = #rrggbb or a theme token (accent, accent sombre, clair, gris clair, gris, gris foncé, sombre, noir) that follows the video theme","type":"object"},"pixels":{"description":"Rows of equal length, 1 char = 1 px, '.' = transparent","items":{"type":"string"},"type":"array"},"primitives":{"description":"kind 3d: [{shape, part, pos, rot, scale, color, shade, segments, motion, ...shape params}] or groups {shape:'group', children:[...]} (format: explainer_library {style:'archibald'}, line custom3d; full schema: explainer_schema)","items":{"type":"object"},"type":"array"}},"required":["name"],"type":"object"},"name":"explainer_create_element","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer create element"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":true,"readOnlyHint":false,"title":"Explainer produce"},"description":"Produce a whole explainer video WITHOUT a human: the server writes the script (compose), validates it (check) and renders it, in the background. ASYNCHRONOUS: returns {job:{id, status:'queued', step, step_label, percent, ...}} right away; then call explainer_job_wait {job_id} until status is done (url = absolute link to the mp4, editor_url = absolute link that opens this video and its version history in the editor, explainer_id, doc) or failed (error). Give exactly ONE source: topic (the server's Claude writes the narration), script (a ready script, as for explainer_render: no AI cost), audio_url or audio_id (the user's own voice-over: transcribed, then composed on it). Takes 3-8 min. Counts against the monthly explainer renders and the monthly AI budget of the plan (HTTP 402 when reached: stop, tell the user), 403 = plan without it or read-only API key (do not retry), 429 = 2 productions already running (retry later). wait:true = block until the end (streams notifications/progress when the request carries _meta.progressToken and Accept: text/event-stream; otherwise ≤ 120 s, then timed_out:true). Call with dry_run:true first to see the cost: same validation and quota checks, nothing created or billed -> {dry_run:true, estimate:{compose, compose_usd_max (AI budget reserved, upper bound in USD), render_count, renders_left, ai_budget_left_usd, duration_s, steps}, would_fail: null | {status: 402|429, error}} (HTTP 200 even when a quota would refuse it).","inputSchema":{"additionalProperties":false,"properties":{"audio_id":{"description":"Id of an audio already imported (explainer_narrate / explainer_import_audio)","type":"string"},"audio_url":{"description":"https:// link to the user's own voice-over (YouTube or a direct audio/video file)","type":"string"},"brand":{"description":"Apply the account's brand kit (default: the kit's own `enabled`)","type":"boolean"},"dry_run":{"default":false,"description":"Estimate only: validation + quota checks, no job, nothing billed (call it first to see the cost)","type":"boolean"},"duration_s":{"description":"Target duration in seconds (topic only), e.g. 45","maximum":180,"minimum":15,"type":"integer"},"script":{"description":"Explainer script. style 'pixel-news' (default): {title, box, theme?, voice:{model, voice, instructions}, scenes:[{say, date, credit, stamp?, elements:[{el, x, y, in, anim, loop, out, exit, k, color?, tint?, parts?, who?, scale?, anchor?, zoom?, ...element params}], camera?:{target: element index, ...}}], cast?, gap?, subscribe_text?} (panel 180x130 px). Word timing: `in`/`out` take seconds OR {word, occurrence?=1, offset?=0, edge?:'end'} = when that word of the scene's `say` is spoken (accents/case/punctuation ignored; resolved from the real voice timing at render). cast: {id: {hair, hair_color, beard, glasses, outfit, colors, party, label...}} and element `who`: id merges those look params under the element's own (element `figure` = configurable person). scale 0.25..6 (default 1), anchor center|bottom; zoom {to, at:[t0,t1], x?, y?} animates one element's zoom. gap = silence between scenes (s, default 0.3, 0..2; always 0 with an imported audio). subscribe_text = end-screen slogan ('' = none). style 'archibald': {style:'archibald', title, theme?, voice, scenes:[{say, text?, big?, tag?, tag_style?, camera?: preset|{move, target, dist, azimuth, elevation, fov, amount, ease, shake}|{keys}, elements:[{el, x, z, y, rot, scale, in, anim, highlight?, to?, move?, ...}], overlays:[{el, x, y | at:[x,y,z], in, ...}]}]} (3D in meters). Other styles (explainer_styles): {style:'<id>', ...} with that style's scene_fields. See explainer_library {style}.","type":"object"},"style":{"default":"pixel-news","description":"Video style id from explainer_styles (pixel-news, archibald, …)","type":"string"},"timeout_s":{"description":"Max seconds to block: ≤ 120 (default 120) in a plain JSON response, ≤ 1800 (default 1800) when progress is streamed (SSE)","maximum":1800.0,"minimum":1,"type":"number"},"title":{"description":"Title shown in the user's list","maxLength":120,"type":"string"},"topic":{"description":"What the video explains, e.g. 'Why is the sky blue'","maxLength":2000,"minLength":3,"type":"string"},"voice":{"description":"Voice-over voice (ElevenLabs), by id or name. French: N6vE6dp4ouYqQlMCBL5j Guillaume (Natural, relaxed storyteller), J50pwizkkVglhJNh30K8 Aslan (Lively social-media tone), jfEwztGDkpbpy89xeku6 Cyp (Soft and calm, for explaining), TojRWZatQyy9dujEdiQ1 Koraly (Expressive storyteller), O31r762Gb3WFygrEOGh0 Victoire (Young and fluid, TikTok tone), 5OnMHwgTFgvPVwE8jP6B Anaïs (Clear and educational, news tone). English: nPczCjzI2devNBz1zQrb Brian (Deep, warm narrator), TX3LPaxmHKxFdv7VOQHJ Liam (Energetic short-form creator), pFZP5JQG7iQjIQuC4Bku Lily (British, clear, documentary tone), cgSgspJ2msm6clMCkdW9 Jessica (Bright and playful). The voice's language is the video's language (pick an English voice for an English script). Old OpenAI names (ash, nova, onyx...) still accepted. Default: the script's voice, else N6vE6dp4ouYqQlMCBL5j.","type":"string"},"wait":{"default":false,"description":"Block until the job ends (see description)","type":"boolean"}},"required":[],"type":"object"},"name":"explainer_produce","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer produce"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Explainer job status"},"description":"Read an agent production job once: {id, status queued|running|done|failed|canceled, step queued|transcribe|compose|check|render|done, step_label (FR), step_label_en, progress 0..1, percent 0..100, message, source, title, agent (API key name), explainer_id, doc, url (absolute link to the mp4, when done), editor_url (absolute link opening this video in the editor), error, cost_usd}. 404 = wrong id or another account's job. Prefer explainer_job_wait.","inputSchema":{"additionalProperties":false,"properties":{"job_id":{"description":"Job id returned by explainer_produce / explainer_template_run (job.id)","type":"string"}},"required":["job_id"],"type":"object"},"name":"explainer_job_status","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer job status"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Explainer job wait"},"description":"Block until an agent production job is done, failed or canceled, then return it like explainer_job_status (url + editor_url when done, error when failed). Without a stream: up to timeout_s ≤ 120 s, then the job with timed_out:true: just call it again. With _meta.progressToken in the request and Accept: text/event-stream, the server streams notifications/progress (progress = percent, total 100, message '<step> — <n> %') and waits up to timeout_s ≤ 1800 s (default 1800).","inputSchema":{"additionalProperties":false,"properties":{"job_id":{"description":"Job id returned by explainer_produce / explainer_template_run (job.id)","type":"string"},"timeout_s":{"description":"Max seconds to block: ≤ 120 (default 120) in a plain JSON response, ≤ 1800 (default 1800) when progress is streamed (SSE)","maximum":1800.0,"minimum":1,"type":"number"}},"required":["job_id"],"type":"object"},"name":"explainer_job_wait","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer job wait"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Explainer jobs"},"description":"List this account's agent production jobs, newest first (same fields as explainer_job_status). active:true = only queued/running ones (at most 2 run at a time).","inputSchema":{"additionalProperties":false,"properties":{"active":{"default":false,"description":"Only queued or running jobs","type":"boolean"}},"required":[],"type":"object"},"name":"explainer_jobs","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer jobs"},{"annotations":{"destructiveHint":true,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":false,"title":"Explainer job cancel"},"description":"Cancel a queued or running agent production job. The job stops at its next step boundary; a compose in progress is stopped within a second and only the Claude calls already made are billed (billed_note gives the real cost, also in job.cost_usd); a render in progress is stopped but still counts in the monthly render quota. Returns the job with status canceled and billed_note (what stays billed); HTTP 409 if it already finished.","inputSchema":{"additionalProperties":false,"properties":{"job_id":{"description":"Job id returned by explainer_produce / explainer_template_run (job.id)","type":"string"}},"required":["job_id"],"type":"object"},"name":"explainer_job_cancel","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer job cancel"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Premium status"},"description":"Premium (HeyGen) features, paid plans only, billed in premium credits (not the monthly quota): balance, price per minute (avatar / dubbing), packs (bought by the user at /premium), consent texts, available/configured.","inputSchema":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"name":"premium_status","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Premium status"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Premium catalog"},"description":"Public HeyGen avatars {id, name, default_voice_id, preview_image_url, preview_video_url, tiers:{standard, premium} (engine or null)}, voices {id, name, language} and translation languages usable by premium_avatar / premium_translate. HTTP 402 on the free plan.","inputSchema":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"name":"premium_catalog","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Premium catalog"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Premium avatar"},"description":"Premium avatar presenter video (HeyGen; costs premium credits, see premium_status). Either an avatar reads `script` (voice_id from premium_catalog, default = the avatar's voice), or it lip-syncs the voice of a rendered explainer (`explainer_id`): the avatar is then overlaid bottom-right in that explainer, and consent:true is required (the voice is the user's, synthetic, or agreed). ALWAYS call with dry_run:true first and show the user the price {credits, duration_s, balance, consent_text}; only then call again without dry_run. Returns a job {id, status}; then premium_job_wait. Errors: 402 = free plan or not enough credits (stop, tell the user), 429 = 2 jobs running or the 24 h cap (30 min avatar, 60 min dubbing) reached (retry later), 400 = fix the arguments.","inputSchema":{"additionalProperties":false,"properties":{"avatar_id":{"type":"string"},"consent":{"default":false,"type":"boolean"},"dry_run":{"default":false,"description":"Estimate only: validation + quota checks, no job, nothing billed (call it first to see the cost)","type":"boolean"},"explainer_id":{"type":"string"},"script":{"maxLength":5000,"type":"string"},"tier":{"default":"premium","description":"standard = Avatar III (40 credits/min), premium = Avatar IV (200 credits/min; Avatar V 300 for a digital twin that supports it). premium_catalog avatars[].tiers gives the engine per tier, null = not supported by that look (HTTP 400).","enum":["standard","premium"],"type":"string"},"voice_id":{"type":"string"}},"required":["avatar_id"],"type":"object"},"name":"premium_avatar","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Premium avatar"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Premium translate"},"description":"Premium: translate and dub a clip with voice cloning and lip-sync (HeyGen; quality speed = 35 credits/min of source, precision = 65 for moving or profile faces), from the clip's latest done export (export_clip first). consent:true is REQUIRED and must come from the user: they are the person speaking or have their written agreement (never for a third party's face without it). Call with dry_run:true first and show the price. Returns a job; then premium_job_wait. Same errors as premium_avatar.","inputSchema":{"additionalProperties":false,"properties":{"clip_id":{"description":"Clip id (UUID)","type":"string"},"consent":{"default":false,"type":"boolean"},"dry_run":{"default":false,"description":"Estimate only: validation + quota checks, no job, nothing billed (call it first to see the cost)","type":"boolean"},"language":{"description":"Language name from premium_catalog, e.g. Spanish","type":"string"},"quality":{"default":"speed","enum":["speed","precision"],"type":"string"}},"required":["clip_id","language"],"type":"object"},"name":"premium_translate","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Premium translate"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Premium job wait"},"description":"Block up to timeout_s (≤ 120) until a premium job is done (url = the mp4, relative to the server) or failed (error; credits are refunded automatically). Call again while it returns queued/running (HeyGen takes a few minutes).","inputSchema":{"additionalProperties":false,"properties":{"job_id":{"description":"Premium job id","type":"string"},"timeout_s":{"default":120.0,"description":"Max seconds to block (server-side long-poll); the object is returned with timed_out:true if still in progress","maximum":120.0,"minimum":1,"type":"number"}},"required":["job_id"],"type":"object"},"name":"premium_job_wait","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Premium job wait"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Explainer my templates"},"description":"List this account's saved parameterized script templates (made with explainer_template_save or 'Save as template' in the editor): [{id, name, description, params:[names of the {{placeholders}}], style, scenes, ...}]. Not the same as explainer_templates (built-in example scripts). Then explainer_template_run {id, values}.","inputSchema":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"name":"explainer_my_templates","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer my templates"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Explainer template save"},"description":"Save a script as a reusable parameterized template: write {{sujet}}, {{chiffre}}… (letters, digits, _) anywhere in its text fields (say, labels, title); they become the template's params. The script is checked on save with sample values (each param's default, else 'exemple'): HTTP 400 'script du modèle invalide' followed by the errors (e.g. an unknown element) = nothing saved, fix and retry. Returns the stored template {id, name, params, ..., warnings (non-blocking)}. HTTP 400 also names an invalid field.","inputSchema":{"additionalProperties":false,"properties":{"description":{"maxLength":300,"type":"string"},"name":{"maxLength":80,"minLength":1,"type":"string"},"script":{"description":"Explainer script. style 'pixel-news' (default): {title, box, theme?, voice:{model, voice, instructions}, scenes:[{say, date, credit, stamp?, elements:[{el, x, y, in, anim, loop, out, exit, k, color?, tint?, parts?, who?, scale?, anchor?, zoom?, ...element params}], camera?:{target: element index, ...}}], cast?, gap?, subscribe_text?} (panel 180x130 px). Word timing: `in`/`out` take seconds OR {word, occurrence?=1, offset?=0, edge?:'end'} = when that word of the scene's `say` is spoken (accents/case/punctuation ignored; resolved from the real voice timing at render). cast: {id: {hair, hair_color, beard, glasses, outfit, colors, party, label...}} and element `who`: id merges those look params under the element's own (element `figure` = configurable person). scale 0.25..6 (default 1), anchor center|bottom; zoom {to, at:[t0,t1], x?, y?} animates one element's zoom. gap = silence between scenes (s, default 0.3, 0..2; always 0 with an imported audio). subscribe_text = end-screen slogan ('' = none). style 'archibald': {style:'archibald', title, theme?, voice, scenes:[{say, text?, big?, tag?, tag_style?, camera?: preset|{move, target, dist, azimuth, elevation, fov, amount, ease, shake}|{keys}, elements:[{el, x, z, y, rot, scale, in, anim, highlight?, to?, move?, ...}], overlays:[{el, x, y | at:[x,y,z], in, ...}]}]} (3D in meters). Other styles (explainer_styles): {style:'<id>', ...} with that style's scene_fields. See explainer_library {style}.","type":"object"}},"required":["name","script"],"type":"object"},"name":"explainer_template_save","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer template save"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Explainer template run"},"description":"Produce a video from a saved template (explainer_my_templates), WITHOUT a human: values fills its {{params}} (every param required, HTTP 400 lists the missing ones). mode fixed = the template script as is with the values substituted (no AI cost, only the render); recompose = the server's Claude re-composes the visuals around the substituted narration (AI budget). ASYNCHRONOUS like explainer_produce: returns {job}; then explainer_job_wait {job_id} until done (url, editor_url) or failed. Same quotas and errors as explainer_produce (402 / 403: stop; 429: retry later). wait:true = block until the end (streams progress like explainer_job_wait). Call with dry_run:true first to see the cost (estimate + would_fail, like explainer_produce; nothing created or billed).","inputSchema":{"additionalProperties":false,"properties":{"brand":{"type":"boolean"},"dry_run":{"default":false,"description":"Estimate only: validation + quota checks, no job, nothing billed (call it first to see the cost)","type":"boolean"},"duration_s":{"description":"recompose only (fixed keeps the template's scenes)","maximum":180,"minimum":15,"type":"integer"},"id":{"description":"Template id (explainer_my_templates)","type":"string"},"mode":{"default":"fixed","enum":["fixed","recompose"],"type":"string"},"style":{"description":"Style id from explainer_styles, recompose only (fixed keeps the template's style)","type":"string"},"timeout_s":{"description":"Max seconds to block: ≤ 120 (default 120) in a plain JSON response, ≤ 1800 (default 1800) when progress is streamed (SSE)","maximum":1800.0,"minimum":1,"type":"number"},"title":{"description":"fixed only: title shown in the user's list (default: the template's)","maxLength":120,"minLength":1,"type":"string"},"values":{"additionalProperties":{"type":["string","number"]},"description":"{param: value}, e.g. {\"sujet\": \"les volcans\", \"chiffre\": 3}","type":"object"},"voice":{"description":"Voice-over voice (ElevenLabs), by id or name. French: N6vE6dp4ouYqQlMCBL5j Guillaume (Natural, relaxed storyteller), J50pwizkkVglhJNh30K8 Aslan (Lively social-media tone), jfEwztGDkpbpy89xeku6 Cyp (Soft and calm, for explaining), TojRWZatQyy9dujEdiQ1 Koraly (Expressive storyteller), O31r762Gb3WFygrEOGh0 Victoire (Young and fluid, TikTok tone), 5OnMHwgTFgvPVwE8jP6B Anaïs (Clear and educational, news tone). English: nPczCjzI2devNBz1zQrb Brian (Deep, warm narrator), TX3LPaxmHKxFdv7VOQHJ Liam (Energetic short-form creator), pFZP5JQG7iQjIQuC4Bku Lily (British, clear, documentary tone), cgSgspJ2msm6clMCkdW9 Jessica (Bright and playful). The voice's language is the video's language (pick an English voice for an English script). Old OpenAI names (ash, nova, onyx...) still accepted. Default: the script's voice, else N6vE6dp4ouYqQlMCBL5j.","type":"string"},"wait":{"default":false,"type":"boolean"}},"required":["id","values"],"type":"object"},"name":"explainer_template_run","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Explainer template run"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Live list sessions"},"description":"List this account's live podcast sessions (ClipOnIt Live), newest first: id, title, status (ready|live|ended), started_at_ms, duration_ms, tracks (recorded tracks count), invite_url; plus quota {minutes_used, minutes_limit} of live minutes this month. Pro (4 h/month, 2 guests) and Business (20 h/month, 3 guests) plans only (HTTP 404 live_disabled while the feature is closed).","inputSchema":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"name":"live_list_sessions","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Live list sessions"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Live create session"},"description":"Create a live podcast session (ClipOnIt Live): returns {id, status:ready, invite_url}. Give invite_url to 1-3 guests (no account needed, they consent to recording before entering); the host opens https://cliponit.app/live/<id> to go live. One open session at a time per account (HTTP 409); HTTP 402 = monthly live minutes used up.","inputSchema":{"additionalProperties":false,"properties":{"format":{"default":"16:9","description":"Episode format","enum":["16:9","9:16"],"type":"string"},"goal":{"description":"Goal of the clips cut from the episode (same as Smart Clipper)","enum":["views","clients","comments","followers"],"type":"string"},"max_guests":{"description":"Guests per live besides the host; default and max = plan (Pro 2, Business 3)","maximum":3,"minimum":1,"type":"integer"},"title":{"maxLength":120,"type":"string"}},"required":[],"type":"object"},"name":"live_create_session","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Live create session"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Live episode status"},"description":"After a ClipOnIt Live session ended: assembly of the episode (runs in the background) {status: queued|running|ready|failed, progress 0-100, error, episode_url (download, 16:9 or 9:16 like the session), tracks: [{id, name, kind, url}] (each participant's separate recording), markers (moments marked during the live), video_id (Smart Clipper video once sent)}.","inputSchema":{"additionalProperties":false,"properties":{"session_id":{"type":"string"}},"required":["session_id"],"type":"object"},"name":"live_episode_status","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Live episode status"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Live send to clipper"},"description":"Send the assembled episode of an ended ClipOnIt Live session to Smart Clipper: creates a source video in the account and starts the analysis like an import (same quotas: monthly hours of video and max length, HTTP 402). Moments marked during the live become clip candidates proposed first (clip.live_marker = true). Returns {video_id, url}; then use wait_video. HTTP 409 while the episode is not ready. Idempotent.","inputSchema":{"additionalProperties":false,"properties":{"session_id":{"type":"string"}},"required":["session_id"],"type":"object"},"name":"live_send_to_clipper","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Live send to clipper"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Partners list campaigns"},"description":"Paid clipping campaigns (ClipOnIt Partnerships): a creator pays per 1,000 views (cpm_cents) from a budget. scope=open (others' open campaigns, default), mine (created by me), joined. Returns {campaigns:[{id, title, cpm_cents, budget_cents, remaining_cents, platforms, rules, goal, ends_at, joined}]}. HTTP 404 partners_disabled while the feature is closed.","inputSchema":{"additionalProperties":false,"properties":{"scope":{"default":"open","enum":["open","mine","joined"],"type":"string"}},"required":[],"type":"object"},"name":"partners_list_campaigns","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Partners list campaigns"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Partners get campaign"},"description":"One campaign: terms, rules, sources, my participation, my_videos (copies of the source in my Smart Clipper) and submissions (all of them for the creator, mine otherwise).","inputSchema":{"additionalProperties":false,"properties":{"campaign_id":{"description":"Campaign id (pc_…)","type":"string"}},"required":["campaign_id"],"type":"object"},"name":"partners_get_campaign","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Partners get campaign"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Partners join campaign"},"description":"Join an open campaign in one call: the source video is copied into my Smart Clipper (no download, not counted in my monthly quota) with the campaign goal and brief, and clip detection starts. Returns {video_ids}: then wait_video, edit/export the clips, publish them on my own accounts with the paid-partnership disclosure on, and call partners_submit. Idempotent.","inputSchema":{"additionalProperties":false,"properties":{"campaign_id":{"description":"Campaign id (pc_…)","type":"string"}},"required":["campaign_id"],"type":"object"},"name":"partners_join_campaign","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Partners join campaign"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Partners submit"},"description":"Submit a published post for a campaign: clip_id must be a clip of my copy of the campaign source; url = full public post URL (TikTok /@account/video/<id>, YouTube shorts/watch, Instagram /reel/<code>; short links refused). A post can be submitted only once (HTTP 409 duplicate). Views are then read from the platform API and the creator approves.","inputSchema":{"additionalProperties":false,"properties":{"campaign_id":{"description":"Campaign id (pc_…)","type":"string"},"clip_id":{"description":"Clip id (UUID)","type":"string"},"url":{"format":"uri","type":"string"}},"required":["campaign_id","clip_id","url"],"type":"object"},"name":"partners_submit","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Partners submit"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Partners earnings"},"description":"Earnings of a campaign, computed at the campaign CPM with its caps: per clipper {views, estimated_cents (pending), validated_cents (approved), paid_cents, due_cents}. The creator sees every clipper, a clipper only his own row. Payments are simulated for now.","inputSchema":{"additionalProperties":false,"properties":{"campaign_id":{"description":"Campaign id (pc_…)","type":"string"}},"required":["campaign_id"],"type":"object"},"name":"partners_earnings","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Partners earnings"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Plan templates"},"description":"Ready-made plans (the workflows ClipOnIt's team runs by hand, locked): name, title, description, params (JSON Schema) and the steps. Pick one and call run_plan {template, vars}.","inputSchema":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"name":"plan_templates","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Plan templates"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Run plan"},"description":"Run a plan server-side: either {template, vars} (from plan_templates) or your own {steps:[{id?, tool, args}], vars?} with the creation tools (args may reference earlier outputs: \"$prev.export_id\", \"$<step id>.moments.0.clip_id\", \"$vars.x\"). The whole plan is validated before anything runs (400 names the step and field). Waits up to timeout_s (≤ 120) then returns the plan state; if status is still running, call plan_status {plan_id}. Result: {plan_id, status running|done|failed, steps:[{tool, ok, output|error}], output (last step's output)}.","inputSchema":{"additionalProperties":false,"properties":{"steps":{"items":{"properties":{"args":{"type":"object"},"id":{"type":"string"},"tool":{"type":"string"}},"required":["tool"],"type":"object"},"maxItems":30,"type":"array"},"template":{"description":"Template name from plan_templates","type":"string"},"timeout_s":{"default":120.0,"description":"Max seconds to block (server-side long-poll); the object is returned with timed_out:true if still in progress","maximum":120.0,"minimum":1,"type":"number"},"vars":{"description":"Template parameters / plan variables","type":"object"},"wait":{"default":true,"type":"boolean"}},"required":[],"type":"object"},"name":"run_plan","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Run plan"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Plan status"},"description":"State of a plan started with run_plan; waits up to timeout_s (≤ 120) for done|failed unless wait:false.","inputSchema":{"additionalProperties":false,"properties":{"plan_id":{"type":"string"},"timeout_s":{"default":120.0,"description":"Max seconds to block (server-side long-poll); the object is returned with timed_out:true if still in progress","maximum":120.0,"minimum":1,"type":"number"},"wait":{"default":true,"type":"boolean"}},"required":["plan_id"],"type":"object"},"name":"plan_status","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Plan status"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Partners accounts"},"description":"My social accounts linked for paid campaigns (only views read by the official API on a linked account are paid): YouTube channels (Google OAuth, youtube.readonly), Instagram professional accounts, the TikTok account connected to ClipOnIt. Max 3 per platform. Linking needs a browser: open https://cliponit.app + platforms.<p>.connect_url while signed in. Returns {max_per_platform, platforms:{youtube|instagram|tiktok:{available, connect_url}}, accounts:[{id, platform, external_id, name}]}.","inputSchema":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"name":"partners_accounts","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Partners accounts"},{"annotations":{"destructiveHint":true,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Partners disconnect account"},"description":"Unlink a YouTube or Instagram account (id pa_… from partners_accounts; its Google token is revoked). Submissions on it stop being verified, so they are no longer paid. TikTok: disconnect in /settings/tiktok.","inputSchema":{"additionalProperties":false,"properties":{"account_id":{"description":"Linked account id (pa_…)","type":"string"}},"required":["account_id"],"type":"object"},"name":"partners_disconnect_account","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Partners disconnect account"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Partners caption code"},"description":"The code (#cliponit-xxxxxx) I must put in the caption (TikTok, Instagram) or title/description (YouTube) of every post I submit to this campaign: views are paid only if the platform API shows it. Requires having joined the campaign.","inputSchema":{"additionalProperties":false,"properties":{"campaign_id":{"description":"Campaign id (pc_…)","type":"string"}},"required":["campaign_id"],"type":"object"},"name":"partners_caption_code","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Partners caption code"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Partners submission views"},"description":"Verified views of one submission: checks {owner (post on my linked account), code (caption code), render (duration = ClipOnIt clip), disclosure (paid-partnership flag when the API exposes it)}, problem (why it is not paid), published_at, window_ends_at (views count until publication + 7 days), verified_views, payable_views (YouTube converted to the campaign CPM when it has a distinct Shorts CPM) and the timestamped view curve.","inputSchema":{"additionalProperties":false,"properties":{"submission_id":{"description":"Submission id (ps_…)","type":"string"}},"required":["submission_id"],"type":"object"},"name":"partners_submission_views","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Partners submission views"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Promo create"},"description":"Product promo video from a SaaS website URL (ClipOnIt 'Pub SaaS'): a headless browser films the public site (landing, features, pricing, demo; robots.txt respected, no login, no form submitted, personal data blurred), reads the brand (colors, fonts, logo) and the promises and prices exactly as written; an LLM writes a 30-60 s storyboard from the site only (hook, problem, demo, proof, price, CTA); voice-over (ElevenLabs); render 16:9 + 9:16; then 3-5 vertical shorts with a hook card. Returns {id, status}. Poll promo_status (several minutes). HTTP 404 promo_disabled while the feature is closed.","inputSchema":{"additionalProperties":false,"properties":{"auto_render":{"default":true,"description":"false: stop at status=storyboard to review it (PUT /api/promos/{id}/storyboard, then POST /api/promos/{id}/render)","type":"boolean"},"brief":{"description":"Optional: audience, angle, what to stress","maxLength":2000,"type":"string"},"duration_s":{"default":45,"maximum":60,"minimum":30,"type":"integer"},"lang":{"default":"fr","enum":["fr","en"],"type":"string"},"url":{"description":"Public website of the SaaS (https://…)","type":"string"},"voice":{"description":"Voice name or id from the catalogue (default: catalogue default of the language)","type":"string"}},"required":["url"],"type":"object"},"name":"promo_create","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Promo create"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Promo status"},"description":"State of a promo: status (exploring | storyboard | rendering | config_ready | done | failed), progress 0-1, site (brand, filmed shots, prices read), storyboard (segments, offers, shorts, warnings = anything not found on the site), outputs (videos {16x9, 9x16} URLs, contact sheets, video_id + shorts [{clip_id, export_id, hook}] — use wait_export), cost {usd, calls}. config_ready = the render engine is not plugged in yet: outputs.config_json is the template config.","inputSchema":{"additionalProperties":false,"properties":{"promo_id":{"description":"Promo id (pr_…)","type":"string"}},"required":["promo_id"],"type":"object"},"name":"promo_status","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Promo status"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":true,"readOnlyHint":false,"title":"Import video"},"description":"Import a public video URL (YouTube, Vimeo, mp4…) and start transcription + moment detection; counts against the monthly quota. detect_moments then waits for it. Output: {video_id, state}","inputSchema":{"additionalProperties":false,"properties":{"brief":{"maxLength":2000,"type":"string"},"goal":{"description":"What the clips are for (changes which moments are picked)","enum":["views","clients","comments","followers"],"type":"string"},"url":{"minLength":8,"type":"string"}},"required":["url"],"type":"object"},"name":"import_video","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Import video"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Detect moments"},"description":"Best moments of an analysed video, as clips (detected after import, sorted by score), or the one moment matching `query` (AI search, creates that clip). The video must be ready (wait_video). Output: {moments:[{clip_id, start_s, end_s, title, score 0-100, reason}]} (waits up to 20 min for the video to be ready)","inputSchema":{"additionalProperties":false,"properties":{"limit":{"default":10,"maximum":50,"minimum":1,"type":"integer"},"query":{"description":"Optional: what to find, in plain words (\"when he reveals the price\")","type":"string"},"video_id":{"description":"Video id","type":"string"}},"required":["video_id"],"type":"object"},"name":"detect_moments","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Detect moments"},{"annotations":{"destructiveHint":true,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Cut silences tics"},"description":"Remove dead air, ums, hesitations, repeats and false starts from the clip edit (cuts written in the edit). Output: the /tics response: {cuts:[{start,end,kind}], removed_s, …}","inputSchema":{"additionalProperties":false,"properties":{"clip_id":{"description":"Clip id (from detect_moments)","type":"string"},"intensity":{"default":"normal","enum":["light","normal","aggressive"],"type":"string"},"orientation":{"default":"vertical","description":"Which edit/export of the clip (9:16 default)","enum":["vertical","horizontal","square","portrait45"],"type":"string"}},"required":["clip_id"],"type":"object"},"name":"cut_silences_tics","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Cut silences tics"},{"annotations":{"destructiveHint":true,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Reframe"},"description":"Reframe the clip from faces, speech and camera cuts (writes layout + keyframes of the edit). mode omitted = the server's default; modes depend on the server version (follow, mix; and auto, speaker_tracking, keynote_split, keynote_speaker where speaker framing is deployed): an unknown mode → 400 with the list the server knows. Output: {layout, keyframes, faces, speakers, shots, mode}","inputSchema":{"additionalProperties":false,"properties":{"clip_id":{"description":"Clip id (from detect_moments)","type":"string"},"framing":{"enum":["tight","medium","wide"],"type":"string"},"mode":{"description":"auto | speaker_tracking | follow | mix | keynote_split | keynote_speaker …","type":"string"},"orientation":{"default":"vertical","description":"Which edit/export of the clip (9:16 default)","enum":["vertical","horizontal","square","portrait45"],"type":"string"}},"required":["clip_id"],"type":"object"},"name":"reframe","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Reframe"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Illustrate with source"},"description":"Internal B-roll: cut-aways taken from the SAME video (the car he talks about, a reaction…), voice continues underneath. Without `inserts`, the AI picks them from the indexed shots; with `inserts`, places exactly those. Output: {broll:[…], inserts:[…]} (the edit's B-roll list)","inputSchema":{"additionalProperties":false,"properties":{"clip_id":{"description":"Clip id (from detect_moments)","type":"string"},"inserts":{"description":"Exact inserts (absolute source seconds): clip range [start_s, end_s] covered by the source from source_s","items":{"additionalProperties":false,"properties":{"end_s":{"type":"number"},"source_s":{"minimum":0,"type":"number"},"start_s":{"type":"number"}},"required":["start_s","end_s","source_s"],"type":"object"},"type":"array"},"orientation":{"default":"vertical","description":"Which edit/export of the clip (9:16 default)","enum":["vertical","horizontal","square","portrait45"],"type":"string"},"source_video_id":{"description":"Optional: take the shots from another of the user's videos","type":"string"}},"required":["clip_id"],"type":"object"},"name":"illustrate_with_source","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Illustrate with source"},{"annotations":{"destructiveHint":true,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Add hook"},"description":"Hook text card shown at the start of the clip (in the edit, rendered by render_clip). For a loop, prefer make_loop's `hook` (stays on screen across the loop). Output: the edit after the change","inputSchema":{"additionalProperties":false,"properties":{"clip_id":{"description":"Clip id (from detect_moments)","type":"string"},"duration_s":{"default":2.5,"maximum":10,"minimum":0.5,"type":"number"},"orientation":{"default":"vertical","description":"Which edit/export of the clip (9:16 default)","enum":["vertical","horizontal","square","portrait45"],"type":"string"},"style":{"default":"card","enum":["card","plain"],"type":"string"},"text":{"maxLength":120,"minLength":1,"type":"string"}},"required":["clip_id","text"],"type":"object"},"name":"add_hook","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Add hook"},{"annotations":{"destructiveHint":true,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Style captions"},"description":"Caption look of the clip edit (only the fields given change). The Standard / Premium render (verified hook, coloured keywords, punch-ins, -14 LUFS) is chosen at render time: render_clip {tier}. Output: the edit after the change","inputSchema":{"additionalProperties":false,"properties":{"animate":{"type":"boolean"},"case":{"enum":["normal","upper","lower"],"type":"string"},"clip_id":{"description":"Clip id (from detect_moments)","type":"string"},"color":{"description":"yellow | white | green | …","type":"string"},"enabled":{"type":"boolean"},"font":{"description":"Inter | Montserrat | Poppins | Bangers","type":"string"},"orientation":{"default":"vertical","description":"Which edit/export of the clip (9:16 default)","enum":["vertical","horizontal","square","portrait45"],"type":"string"},"position":{"enum":["top","middle","bottom"],"type":"string"},"size":{"maximum":5,"minimum":1,"type":"integer"},"style":{"enum":["karaoke","boxed","outline","glow","minimal","behind","serif","hollow","highlight"],"type":"string"},"words_per_line":{"maximum":8,"minimum":1,"type":"integer"}},"required":["clip_id"],"type":"object"},"name":"style_captions","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Style captions"},{"annotations":{"destructiveHint":true,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Edit clip"},"description":"Small exact edits: new bounds, extra cuts, captions hidden on ranges, music removed. Absolute source seconds. Output: {clip, edit}","inputSchema":{"additionalProperties":false,"properties":{"add_cuts":{"description":"Ranges removed from the clip (appended to existing cuts)","items":{"additionalProperties":false,"properties":{"end_s":{"minimum":0,"type":"number"},"start_s":{"minimum":0,"type":"number"}},"required":["start_s","end_s"],"type":"object"},"type":"array"},"clip_id":{"description":"Clip id (from detect_moments)","type":"string"},"end_s":{"description":"New clip end","type":"number"},"hide_captions":{"description":"Ranges where captions are hidden","items":{"additionalProperties":false,"properties":{"end_s":{"minimum":0,"type":"number"},"start_s":{"minimum":0,"type":"number"}},"required":["start_s","end_s"],"type":"object"},"type":"array"},"music":{"description":"Set the music bed: {music_id, volume, ducking, fade_s}","type":"object"},"orientation":{"default":"vertical","description":"Which edit/export of the clip (9:16 default)","enum":["vertical","horizontal","square","portrait45"],"type":"string"},"remove_music":{"description":"true = remove the music bed","type":"boolean"},"start_s":{"description":"New clip start","type":"number"}},"required":["clip_id"],"type":"object"},"name":"edit_clip","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Edit clip"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Render clip"},"description":"Render the clip edit to MP4 and (by default) wait for it. tier=premium = Premium render where available (paid plans). Output: {export_id, status done|failed|queued|running, url, duration_s}","inputSchema":{"additionalProperties":false,"properties":{"clip_id":{"description":"Clip id (from detect_moments)","type":"string"},"music":{"description":"false = no music added by ClipOnIt (clip sound kept); add a trending sound in TikTok / Instagram","type":"boolean"},"orientation":{"default":"vertical","description":"Which edit/export of the clip (9:16 default)","enum":["vertical","horizontal","square","portrait45"],"type":"string"},"tics":{"description":"Also cut ums at render time","enum":["light","normal","aggressive"],"type":"string"},"tier":{"default":"standard","enum":["standard","premium"],"type":"string"},"timeout_s":{"default":1200,"maximum":1200,"minimum":10,"type":"integer"},"wait":{"default":true,"type":"boolean"}},"required":["clip_id"],"type":"object"},"name":"render_clip","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Render clip"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Make loop"},"description":"TikTok loop with rewind: opens on the payoff [loop_at_s, end], rewinds the whole clip in rewind_s (desaturated, scanlines, shake, rewound sound, text), then plays [0, loop_at_s[ so the last frame meets the first. New export. Output: {export_id, url, duration_s, parent_id, loop_at_s, seam_s}","inputSchema":{"additionalProperties":false,"properties":{"export_id":{"description":"Export id (from render_clip or a previous post-processing tool)","type":"string"},"hook":{"description":"Optional hook kept on screen for the whole loop (render the clip WITHOUT add_hook then)","maxLength":80,"type":"string"},"loop_at_s":{"description":"Export time where the payoff starts (default: start of the last sentence)","minimum":0.5,"type":"number"},"rewind_s":{"default":1.2,"maximum":3,"minimum":0.5,"type":"number"},"rewind_text":{"default":"◀◀ UN PEU PLUS TÔT…","maxLength":40,"type":"string"}},"required":["export_id"],"type":"object"},"name":"make_loop","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Make loop"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Add music beat"},"description":"Musical beat: inserts at at_s a 1.5-4 s shot WITHOUT speech (a visual moment of the source video) under music or its real sound, then the clip resumes. New export. Output: {export_id, url, duration_s, parent_id, at_s, source_s}","inputSchema":{"additionalProperties":false,"properties":{"at_s":{"description":"Export time where the beat is inserted (default: the first pause between sentences after 40 % of the clip)","minimum":0,"type":"number"},"crop_x":{"default":0.5,"maximum":1,"minimum":0,"type":"number"},"duration_s":{"default":2.5,"maximum":6,"minimum":1,"type":"number"},"export_id":{"description":"Export id (from render_clip or a previous post-processing tool)","type":"string"},"framing":{"default":"fit","description":"fit = whole shot on a blurred background; crop = fill the frame around crop_x","enum":["fit","crop"],"type":"string"},"music_id":{"description":"Optional music from /api/music (the user's library); without it the shot keeps its real sound","type":"string"},"music_volume":{"default":0.6,"maximum":1,"minimum":0,"type":"number"},"source_s":{"description":"Start of the shot in the SOURCE video (absolute s). Default: the source moment under at_s","type":"number"},"source_volume":{"description":"Real sound of the shot (default 0.25 with music, 1 without)","maximum":1,"minimum":0,"type":"number"}},"required":["export_id"],"type":"object"},"name":"add_music_beat","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Add music beat"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Check dark shots"},"description":"Too-dark ranges of an export (mean luma per 0.25 s, 0-255). Output: {mean_luma, dark:[{start_s, end_s, mean_luma}], threshold}","inputSchema":{"additionalProperties":false,"properties":{"export_id":{"description":"Export id (from render_clip or a previous post-processing tool)","type":"string"},"min_s":{"default":1.0,"minimum":0.25,"type":"number"},"threshold":{"default":50,"maximum":255,"minimum":1,"type":"number"}},"required":["export_id"],"type":"object"},"name":"check_dark_shots","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Check dark shots"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Brighten"},"description":"Brighten ranges of an export (eq brightness + gamma). Without ranges: the ranges check_dark_shots finds. New export (or the same one, changed:false, when nothing is dark). Output: {export_id, url, changed, ranges}","inputSchema":{"additionalProperties":false,"properties":{"export_id":{"description":"Export id (from render_clip or a previous post-processing tool)","type":"string"},"ranges":{"description":"Export-time ranges to brighten","items":{"additionalProperties":false,"properties":{"end_s":{"minimum":0,"type":"number"},"start_s":{"minimum":0,"type":"number"}},"required":["start_s","end_s"],"type":"object"},"type":"array"},"strength":{"default":1,"maximum":3,"minimum":0.2,"type":"number"}},"required":["export_id"],"type":"object"},"name":"brighten","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Brighten"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Critique clip"},"description":"Review an export before posting, like an organic editor: measures (cuts, loudness, silences, on-screen text, faces, loop seam, darkness), vision judgement of keyframes + transcript (model chosen by the server: CRITIQUE_MODEL), scored checklist (blocks A-F, I), what to remove, timed fixes, contact sheet. Output: {export_id, score 0-100, blocks, capped, intention, summary, original_contribution, to_remove[], fixes[{start_s,end_s,action,value,detail,why,severity}], organic_editor[], checklist[], cost, contact_sheet_url, report_url}","inputSchema":{"additionalProperties":false,"properties":{"compare_to":{"description":"Optional: an earlier export already critiqued (v1) → `comparison` {before, after, verdict keep|reject, why}: keep v2 only if +5 points without losing a blocking criterion","type":"string"},"export_id":{"description":"Export id (from render_clip or a previous post-processing tool)","type":"string"},"intention":{"description":"Optional: what the clip should make people feel/learn","type":"string"},"vision":{"default":true,"description":"false = measures + automatic checklist only (free, no model call)","type":"boolean"},"with_source":{"default":true,"description":"Also look at the source ±5 min for unused strong visual moments","type":"boolean"}},"required":["export_id"],"type":"object"},"name":"critique_clip","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Critique clip"},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Predict clip"},"description":"Honest potential: quality score and its percentile among reference shorts, an 80 % interval whose width is MEASURED, the reliability of that measure (often 'none': then the honest forecast is the account's usual distribution), and the view range on this account if its history is known. Never an invented view count. Output: {quality_score, score_source, quality_percentile, percentile_80, reliability, calibration, views_if_posted?}","inputSchema":{"additionalProperties":false,"properties":{"export_id":{"description":"Export id (from render_clip or a previous post-processing tool)","type":"string"},"followers":{"minimum":0,"type":"integer"},"history_views":{"description":"Views of the account's last shorts (default: the user's TikTok posts tracked by ClipOnIt, if ≥ 5)","items":{"minimum":0,"type":"number"},"type":"array"}},"required":["export_id"],"type":"object"},"name":"predict_clip","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Predict clip"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Improve clip"},"description":"Turn the last critique_clip of an export into fixes the engines can apply and a ready-to-run plan (POST /api/agent/plan): edit fixes, render_clip, post-processing (brighten, musical beats, loop), critique again. Keep v2 only if the score rises ≥ 5 points without losing a blocking criterion. Output: {score, fixes[], plan:{steps}|null, not_applicable[]}","inputSchema":{"additionalProperties":false,"properties":{"export_id":{"description":"Export id (from render_clip or a previous post-processing tool)","type":"string"}},"required":["export_id"],"type":"object"},"name":"improve_clip","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Improve clip"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Produce explainer"},"description":"Generate a full vertical explainer / ad video from a brief (server writes the script, voice, render) and wait for it (agent production, /api/agent/produce). Output: {job_id, status, url, editor_url}","inputSchema":{"additionalProperties":false,"properties":{"duration_s":{"maximum":180,"minimum":15,"type":"integer"},"style":{"description":"Style id (explainer_styles), default pixel-news","type":"string"},"title":{"maxLength":120,"type":"string"},"topic":{"description":"What the video says (for an ad: product, URL, promise, audience)","maxLength":4000,"minLength":3,"type":"string"}},"required":["topic"],"type":"object"},"name":"produce_explainer","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Produce explainer"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Partners join"},"description":"Join a paid clipping campaign (Partnerships): the creator's source is copied into the user's videos with the campaign goal and brief; detection starts. Then detect_moments on the returned video. Output: {video_id, video_ids}","inputSchema":{"additionalProperties":false,"properties":{"campaign_id":{"description":"Campaign id (pc_…)","type":"string"}},"required":["campaign_id"],"type":"object"},"name":"partners_join","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Partners join"},{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Publish tiktok"},"description":"Prepare a TikTok post of an export. Posting needs the account owner's confirmation in ClipOnIt (TikTok consent, privacy, disclosure): returns the link the user opens to confirm; nothing is posted by an API key. Output: {status:'needs_user_confirmation', export_id, confirm_url}","inputSchema":{"additionalProperties":false,"properties":{"export_id":{"description":"Export id (from render_clip or a previous post-processing tool)","type":"string"},"title":{"maxLength":150,"type":"string"}},"required":["export_id"],"type":"object"},"name":"publish_tiktok","securitySchemes":[{"scopes":["clips"],"type":"oauth2"}],"title":"Publish tiktok"}]}