{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/clueso-io/json-schema/clueso-io-mcp-tool-inputs.json",
  "title": "Clueso MCP tool input schemas",
  "description": "inputSchema of each tool, copied verbatim from an anonymous tools/list response of the documented Clueso MCP server https://connect.clueso.io/mcp (serverInfo clueso-connect 1.0.0) on 2026-11-04.",
  "x-source": "https://connect.clueso.io/mcp",
  "x-method": "probed",
  "$defs": {
    "get_design_guide": {
      "type": "object",
      "properties": {
        "topic": {
          "description": "'design' (default): the house style, shaped by select. 'pages': authoring HTML animation pages. 'skills': the computer's skills, for inspiration.",
          "type": "string",
          "enum": [
            "design",
            "pages",
            "skills"
          ]
        },
        "select": {
          "description": "Everything to return, in one list: section paths and plain words, mixed. 'brand' and 'brand.<section>' read the brand kit.",
          "minItems": 1,
          "maxItems": 40,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          }
        },
        "skill": {
          "description": "topic 'skills': a skill's name from the list; returns its SKILL.md and its files.",
          "type": "string"
        },
        "path": {
          "description": "topic 'skills' with skill: a file inside it, e.g. 'references/x.md'.",
          "type": "string"
        },
        "project_id": {
          "description": "The project you are designing: adds its brand kit (the project's own, else the workspace default) as the 'brand' section.",
          "type": "string"
        },
        "brand_kit_id": {
          "description": "Read this brand kit (an id from find(type='brand_kits'), or 'default') instead of the project's: for a project on another kit, or a co-branded scene.",
          "type": "string"
        },
        "clueprint_id": {
          "description": "The clueprint this project is built from: the brand section then says what it decides (format) and what the kit decides (identity), and whether it is brand_locked.",
          "type": "string"
        }
      },
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "read_animation": {
      "type": "object",
      "properties": {
        "project_id": {
          "description": "The project. Alone (no element_id/animation): lists its animations.",
          "type": "string"
        },
        "element_id": {
          "description": "An animation element: reads the page version it shows (a canvas Mograph: its code and parameters, read-only). Needs project_id.",
          "type": "string"
        },
        "animation": {
          "type": "string",
          "minLength": 1,
          "description": "The animation (a page site): its id from read_animation, or its site name in this project. Or name it by element_id instead."
        },
        "rev": {
          "description": "A revision other than the one shown.",
          "type": "integer",
          "minimum": 1,
          "maximum": 9007199254740991
        },
        "path": {
          "description": "A file of the site, e.g. 'index.html' or 'scripts/scene.js'.",
          "type": "string"
        },
        "format": {
          "description": "With path: 'numbered' (default) prefixes 'N| '; 'raw' is the exact text.",
          "type": "string",
          "enum": [
            "numbered",
            "raw"
          ]
        },
        "range": {
          "description": "With path: first and last line, inclusive, e.g. [40, 80].",
          "minItems": 2,
          "maxItems": 2,
          "type": "array",
          "items": {
            "type": "integer",
            "minimum": 1,
            "maximum": 9007199254740991
          }
        },
        "pattern": {
          "description": "A regular expression, at most 200 characters: matching lines with path and line number.",
          "type": "string",
          "maxLength": 200
        },
        "all": {
          "description": "With pattern and no animation: search every page site of the workspace.",
          "type": "boolean"
        }
      },
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "edit_animation": {
      "type": "object",
      "properties": {
        "project_id": {
          "description": "The project: a passing save moves its uses (one undo step).",
          "type": "string"
        },
        "element_id": {
          "description": "The element whose page to edit (from the version it shows); a canvas Mograph is upgraded to a page first. Needs project_id.",
          "type": "string"
        },
        "animation": {
          "description": "The site to edit when there is no element; 'new' makes one (name, scope).",
          "type": "string",
          "minLength": 1
        },
        "scope": {
          "description": "animation 'new' only: 'project' (default with project_id), 'element' (with for_element), or 'library' ONLY when the user asks to keep it for other videos.",
          "type": "string",
          "enum": [
            "element",
            "project",
            "library"
          ]
        },
        "name": {
          "description": "animation 'new': its name.",
          "type": "string"
        },
        "slug": {
          "description": "animation 'new': its site name (a-z, 0-9, -).",
          "type": "string"
        },
        "folder": {
          "description": "animation 'new' with scope 'library': folder path, e.g. 'Brand/Intros'.",
          "type": "string"
        },
        "for_element": {
          "description": "animation 'new' with scope 'element'.",
          "type": "string"
        },
        "base_rev": {
          "description": "The revision you read; a save made since is refused (stale_base).",
          "type": "integer",
          "minimum": 1,
          "maximum": 9007199254740991
        },
        "target": {
          "description": "When the site is used outside this project: 'shared' changes it for everyone, 'fork' copies it for this project.",
          "type": "string",
          "enum": [
            "shared",
            "fork"
          ]
        },
        "confirm_breaking": {
          "description": "Move this project's uses even when the new version drops something they use (a removed or retyped parameter, action, cue or page, a new size); without it such a save moves nothing and lists `breaking`.",
          "type": "boolean"
        },
        "patch": {
          "description": "A unified diff of any number of files (git diff / diff -ruN), from the site folder.",
          "type": "string",
          "minLength": 1
        },
        "files": {
          "description": "Per-file changes: exactly one of edits, content, patch or upload_id each.",
          "minItems": 1,
          "maxItems": 100,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "path": {
                "type": "string",
                "minLength": 1,
                "description": "File path in the site, e.g. 'index.html'."
              },
              "edits": {
                "description": "Exact-match replaces, applied in order.",
                "minItems": 1,
                "maxItems": 100,
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "old": {
                      "type": "string",
                      "minLength": 1,
                      "description": "Exact text to find (without the 'N| ' numbers); must occur once unless replace_all."
                    },
                    "new": {
                      "type": "string",
                      "description": "The text to put in its place."
                    },
                    "replace_all": {
                      "description": "Replace every occurrence.",
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "old",
                    "new"
                  ]
                }
              },
              "content": {
                "description": "The whole file (new file or rewrite).",
                "type": "string"
              },
              "patch": {
                "description": "A unified diff of this one file.",
                "type": "string"
              },
              "upload_id": {
                "description": "A file sent with upload_file: text is used as the whole content (UTF-8, at most 2 MB); media (video, audio, image, font, model) is added as is (at most 200 MB).",
                "type": "string",
                "pattern": "^mup_"
              }
            },
            "required": [
              "path"
            ]
          }
        },
        "upload_ids": {
          "description": "Whole files sent with upload_file(file_names); each upload's name is its path.",
          "minItems": 1,
          "maxItems": 100,
          "type": "array",
          "items": {
            "type": "string",
            "pattern": "^mup_"
          }
        },
        "edits": {
          "description": "Ops applied first: delete and move (replace and write work too).",
          "minItems": 1,
          "maxItems": 100,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "op": {
                "type": "string",
                "enum": [
                  "replace",
                  "write",
                  "delete",
                  "move"
                ]
              },
              "path": {
                "type": "string",
                "minLength": 1
              },
              "old": {
                "type": "string"
              },
              "new": {
                "type": "string"
              },
              "all": {
                "type": "boolean"
              },
              "content": {
                "type": "string"
              },
              "to": {
                "description": "move: the new path.",
                "type": "string"
              }
            },
            "required": [
              "op",
              "path"
            ]
          }
        },
        "dry_run": {
          "description": "Check without saving; returns a preview token for get_clip render.preview.",
          "type": "boolean"
        },
        "probe": {
          "description": "With dry_run: also render it in the probe (slower; returns its contact sheet).",
          "type": "boolean"
        },
        "detail": {
          "description": "'full' adds the diff hunks and thumbnail records.",
          "type": "string",
          "enum": [
            "summary",
            "full"
          ]
        },
        "preview": {
          "description": "Omitted: the probe's contact sheet (the page at its defaults). true: render what the save produced IN PLACE -- the element it moved (a dry run: its unsaved page), cropped, at four moments. {filmstrip, video, test, element_id}: the same as a filmstrip, with a short MP4 link, or with test settings. false: no image.",
          "anyOf": [
            {
              "type": "boolean"
            },
            {
              "type": "object",
              "properties": {
                "filmstrip": {
                  "anyOf": [
                    {
                      "type": "boolean",
                      "const": true
                    },
                    {
                      "type": "object",
                      "properties": {
                        "from": {
                          "description": "Window start, element-local seconds, or clip seconds for a whole clip (default 0).",
                          "type": "number",
                          "minimum": 0
                        },
                        "to": {
                          "description": "Window end, element-local seconds, or clip seconds for a whole clip (default the end).",
                          "type": "number",
                          "exclusiveMinimum": 0
                        },
                        "around": {
                          "description": "Center the window on a cue ('cue:land') or an action clip ('action:hop', by name or id) instead of from/to. On a whole clip, a cue several animations share is 'cue:land@<element name>'.",
                          "type": "string",
                          "pattern": "^(cue|action):.+"
                        },
                        "span": {
                          "description": "With around: the window's length in seconds (default 2 around a cue; the clip plus 0.5 around an action).",
                          "type": "number",
                          "exclusiveMinimum": 0
                        },
                        "frames": {
                          "description": "How many frames across the window, first and last included (default 12, max 16).",
                          "type": "integer",
                          "minimum": 2,
                          "maximum": 16
                        },
                        "fps": {
                          "description": "Frames per second instead of a count, e.g. 8 over 2 s is 17, capped at 16.",
                          "type": "number",
                          "exclusiveMinimum": 0,
                          "maximum": 60
                        },
                        "seam": {
                          "description": "get_clip of a whole clip only: the cut from this clip into the next, as the player shows it: this clip's last second, its transition, and the next clip's first second, in this clip's seconds; takes frames, no window. Every cut of the film at once: get_project({render: {seams: true}}).",
                          "type": "boolean",
                          "const": true
                        }
                      },
                      "additionalProperties": false
                    }
                  ],
                  "description": "See motion in ONE image instead of four moments: frames spaced evenly across a window (default 12 over the whole animation), in reading order, each labelled with its element-local time, with a thin timeline under each row marking the frames, cues (lines) and action clips (bars). Read pacing, easing and whether a beat lands on its cue from it. true for the defaults, or {from, to} / {around: 'cue:<key>' | 'action:<name>', span}, with frames or fps. Replaces timestamps. On get_clip without element_id or preview it shows the whole clip, every element drawn, in clip seconds, and the timeline also ticks each element's start and end. HTML animations (pages) are drawn from their page in every render, the next clip's across a seam included; their cues and actions are the page's."
                },
                "video": {
                  "description": "For people: also a short low-resolution MP4 of the same window (the filmstrip's, or the animation's first 10 s), returned as video_url, a link valid for an hour. You can't watch it: judge motion from the filmstrip.",
                  "type": "boolean"
                },
                "test": {
                  "type": "object",
                  "properties": {
                    "parameter_values": {
                      "description": "Values to try, over the element's own (an image or video value: a URL or upload).",
                      "type": "object",
                      "propertyNames": {
                        "type": "string"
                      },
                      "additionalProperties": {}
                    },
                    "param_tracks": {
                      "description": "Keyframes to try for animatable parameters: {key: [{t, value, interpolation?}]}.",
                      "type": "object",
                      "propertyNames": {
                        "type": "string"
                      },
                      "additionalProperties": {
                        "minItems": 1,
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "t": {
                              "type": "number",
                              "minimum": 0,
                              "description": "Element-local (page) seconds."
                            },
                            "value": {},
                            "interpolation": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "t",
                            "value"
                          ],
                          "additionalProperties": {}
                        }
                      }
                    },
                    "actions": {
                      "description": "Action clips to try instead of the element's: [{name, start, duration?, args?}].",
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string",
                            "minLength": 1
                          },
                          "start": {
                            "type": "number",
                            "minimum": 0
                          },
                          "duration": {
                            "type": "number",
                            "exclusiveMinimum": 0
                          },
                          "args": {
                            "type": "object",
                            "propertyNames": {
                              "type": "string"
                            },
                            "additionalProperties": {}
                          }
                        },
                        "required": [
                          "name",
                          "start"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "cues": {
                      "description": "Cue times to try: {key: seconds}.",
                      "type": "object",
                      "propertyNames": {
                        "type": "string"
                      },
                      "additionalProperties": {
                        "type": "number",
                        "minimum": 0
                      }
                    }
                  },
                  "additionalProperties": false,
                  "description": "Try other settings on the one animation rendered (element_id or preview), nothing saved: parameter_values, param_tracks, actions, cues, in element-local (page) seconds. For an HTML animation the page is drawn with them; check a value or a cue before setting it with update_elements."
                },
                "element_id": {
                  "description": "Which use to render when the save moved several (default the first).",
                  "type": "string",
                  "minLength": 1
                }
              },
              "additionalProperties": false
            }
          ]
        },
        "request": {
          "description": "One line: what the user asked for (history).",
          "type": "string"
        },
        "checkpoint": {
          "description": "A name for a state worth keeping (e.g. 'v1 layout'): labels the saved revision and adds it as a version to the animation's Generations history, where the user can find and restore it.",
          "type": "string",
          "minLength": 1,
          "maxLength": 80
        },
        "place": {
          "description": "Put the saved page on a clip in this call (after a passing save): returns element_id. Needs project_id. Without file changes: places the site as it is.",
          "type": "object",
          "properties": {
            "clip_index": {
              "description": "The clip, 0-based.",
              "type": "integer",
              "minimum": 0,
              "maximum": 9007199254740991
            },
            "clip_id": {
              "description": "Or the clip by id.",
              "type": "string",
              "minLength": 1
            },
            "x": {
              "description": "Canvas px; default 0.",
              "type": "number"
            },
            "y": {
              "description": "Canvas px; default 0.",
              "type": "number"
            },
            "width": {
              "description": "Canvas px; default full frame.",
              "type": "number",
              "exclusiveMinimum": 0
            },
            "height": {
              "description": "Canvas px; default full frame.",
              "type": "number",
              "exclusiveMinimum": 0
            },
            "start_time": {
              "description": "Clip seconds; default 0.",
              "type": "number",
              "minimum": 0
            },
            "end_time": {
              "description": "Clip seconds; default the page's duration.",
              "type": "number",
              "exclusiveMinimum": 0
            },
            "entry": {
              "description": "Another page of the site, e.g. 'outro.html'.",
              "type": "string",
              "minLength": 1
            },
            "name": {
              "description": "The element's label in the editor (default: the site's name, or site/entry).",
              "type": "string",
              "minLength": 1
            },
            "continue_from": {
              "description": "An animation element of an earlier clip: carry it on into the next clip (or clip_index) from where it stopped, with its box, values and timeline.",
              "type": "string",
              "minLength": 1
            }
          },
          "additionalProperties": false
        },
        "wait_s": {
          "description": "How long to wait for the probe's verdict (default the server's).",
          "type": "number",
          "minimum": 0,
          "maximum": 170
        }
      },
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "create_project": {
      "type": "object",
      "properties": {
        "title": {
          "description": "Project title",
          "type": "string"
        },
        "folder_id": {
          "description": "Optional folder ID to nest the project under. Use find(type='folders') to discover IDs. Omit to create at workspace root.",
          "type": "string"
        },
        "description": {
          "description": "Optional project description (max ~500 chars)",
          "type": "string"
        },
        "brand_kit_id": {
          "description": "Brand kit for this project: an id from find(type='brand_kits'), or 'default' (the same as omitting it: follow the workspace default kit, including when the default changes). Setting a kit needs an editor or admin.",
          "type": "string"
        },
        "script": {
          "description": "Voiceover script; one blank clip per paragraph. Not with `audio`.",
          "type": "string"
        },
        "audio": {
          "description": "An audio upload (an mcp_upload_id or a Clueso upload_id) to use as the voiceover (original narration, transcribed). Not with `script`.",
          "type": "string"
        }
      },
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "update_project": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "string",
          "description": "The project ID to update"
        },
        "title": {
          "description": "New project title",
          "type": "string"
        },
        "description": {
          "description": "New description",
          "type": "string"
        },
        "folder_id": {
          "description": "New folder ID. Pass '' (empty string) to move to workspace root.",
          "type": "string"
        },
        "aspect_ratio": {
          "description": "New aspect ratio as 'W:H' (positive integers). Common: '16:9' landscape, '9:16'/'4:5'/'3:4' portrait, '1:1' square.",
          "type": "string",
          "pattern": "^[1-9]\\d*:[1-9]\\d*$"
        },
        "add_tags": {
          "description": "Tag names to add; missing tags are created.",
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 40
          }
        },
        "remove_tags": {
          "description": "Tag names to remove.",
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 40
          }
        },
        "brand_kit_id": {
          "description": "Brand kit for this project: an id from find(type='brand_kits'), or 'default' to follow the workspace default kit.",
          "type": "string",
          "minLength": 1
        }
      },
      "required": [
        "project_id"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "duplicate_project": {
      "type": "object",
      "properties": {
        "source_project_id": {
          "type": "string",
          "description": "Project (guide) ID to duplicate."
        },
        "new_title": {
          "description": "Override the title of the duplicate.",
          "type": "string"
        },
        "folder_id": {
          "description": "Place the duplicate in this folder. Omit to keep the same folder as the source. Pass '' (empty string) to move to workspace root.",
          "type": "string"
        }
      },
      "required": [
        "source_project_id"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "export_project": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "string",
          "description": "The project ID to export"
        },
        "resolution": {
          "description": "Output resolution (default 1080p)",
          "type": "string",
          "enum": [
            "720p",
            "1080p",
            "2k",
            "4k"
          ]
        },
        "fps": {
          "description": "Frames per second (default 30)",
          "anyOf": [
            {
              "type": "number",
              "const": 24
            },
            {
              "type": "number",
              "const": 30
            },
            {
              "type": "number",
              "const": 60
            }
          ]
        },
        "captions": {
          "description": "Burn subtitles into the video (default false)",
          "type": "boolean"
        },
        "format": {
          "description": "\"mp4\" (video, default) or \"mp3\" (the whole soundtrack, no picture; download only)",
          "type": "string",
          "enum": [
            "mp4",
            "mp3"
          ]
        },
        "language": {
          "description": "Language code of the voiceover and captions to render, e.g. 'de' for a translated project (default 'en'). Downloads only.",
          "type": "string",
          "minLength": 2
        },
        "destination": {
          "description": "Where the render goes: \"download\" (mp4 file, default) or \"embed\" (publish/update the project's embeddable player; its URL never changes). Publishing an embed needs an editor or admin role.",
          "type": "string",
          "enum": [
            "download",
            "embed"
          ]
        }
      },
      "required": [
        "project_id"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "get_export": {
      "type": "object",
      "properties": {
        "export_id": {
          "description": "The export_id export_project returned. Omit when passing project_id.",
          "type": "string",
          "format": "uuid",
          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
        },
        "project_id": {
          "description": "Check this project's most recently requested export instead. Omit when passing export_id.",
          "type": "string"
        },
        "wait_seconds": {
          "description": "How long to wait for the export to complete or fail before answering, default 50s. Pass 0 for an immediate status check (always, inside run_script).",
          "type": "integer",
          "minimum": 0,
          "maximum": 55
        }
      },
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "get_recording_activity": {
      "type": "object",
      "properties": {
        "project_id": {
          "description": "Only this project's row.",
          "type": "string"
        },
        "state": {
          "description": "Only rows in this state.",
          "type": "string",
          "enum": [
            "pending",
            "in_progress",
            "done",
            "failed"
          ]
        },
        "limit": {
          "description": "Rows to return, default 20, max 50. `total` counts every matching row.",
          "type": "integer",
          "minimum": 1,
          "maximum": 50
        }
      },
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "get_embed": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "string",
          "description": "The project ID whose embed to read"
        }
      },
      "required": [
        "project_id"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "create_folder": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 255,
          "description": "Folder name"
        },
        "parent_folder_id": {
          "description": "Create the folder inside this folder (use find(type='folders') for IDs). Omit for the workspace root.",
          "type": "string"
        },
        "description": {
          "description": "Optional folder description",
          "type": "string",
          "maxLength": 500
        }
      },
      "required": [
        "name"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "find": {
      "type": "object",
      "properties": {
        "type": {
          "type": "string",
          "enum": [
            "projects",
            "folders",
            "tags",
            "clueprints",
            "workspaces",
            "backgrounds",
            "voices",
            "fonts",
            "brand_kits",
            "icons",
            "image_gen_style_packs",
            "code_style_packs",
            "element_components",
            "animations",
            "avatars",
            "images",
            "videos",
            "music",
            "sfx",
            "clip_templates"
          ],
          "description": "Resource type to find."
        },
        "query": {
          "description": "Case-insensitive substring match on name.",
          "type": "string"
        },
        "id": {
          "description": "Exact id \u2014 returns just that record.",
          "type": "string"
        },
        "folder_id": {
          "description": "projects/images/videos/music/clip_templates only: restrict to a folder.",
          "type": "string"
        },
        "source": {
          "description": "media types (images/videos/music/sfx): where to search \u2014 'org' (your saved workspace media), 'stock' (stock library \u2014 images/videos via Pexels, music from Clueso's catalog), 'all' (both; default). sfx is stock-only. clip_templates: 'defaults' | 'org' | 'library' | 'all'. voices: 'saved' (only this workspace's saved voices) | 'own' (its saved and custom voices).",
          "type": "string",
          "enum": [
            "org",
            "stock",
            "all",
            "defaults",
            "library",
            "saved",
            "own"
          ]
        },
        "clip_label": {
          "description": "clip_templates only: one kind of cover.",
          "type": "string",
          "enum": [
            "intro",
            "outro",
            "chapter break",
            "others"
          ]
        },
        "engine": {
          "description": "voices only: 'eleven' | 'cartesia' | 'google'.",
          "type": "string"
        },
        "language": {
          "description": "voices only: e.g. 'en'.",
          "type": "string"
        },
        "tags": {
          "description": "voices: descriptive words (a list or comma-separated), any one matches. projects: tag names; keeps projects carrying them (see tag_match).",
          "anyOf": [
            {
              "maxItems": 50,
              "type": "array",
              "items": {
                "type": "string",
                "minLength": 1,
                "maxLength": 40
              }
            },
            {
              "type": "string"
            }
          ]
        },
        "creator_id": {
          "description": "clueprints only: filter by creator id.",
          "type": "string"
        },
        "mine_only": {
          "description": "clueprints only: only ones I created.",
          "type": "boolean"
        },
        "orientation": {
          "description": "stock images/videos only: result orientation.",
          "type": "string",
          "enum": [
            "landscape",
            "portrait",
            "square"
          ]
        },
        "color": {
          "description": "stock images only: color name or hex, e.g. 'blue'.",
          "type": "string"
        },
        "size": {
          "description": "stock videos only: minimum resolution bucket.",
          "type": "string",
          "enum": [
            "large",
            "medium",
            "small"
          ]
        },
        "min_duration": {
          "description": "stock videos only: minimum clip length in seconds.",
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991
        },
        "max_duration": {
          "description": "stock videos + freesound audio: maximum clip length in seconds.",
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991
        },
        "provider": {
          "description": "stock media: which library to search (exactly one). images/videos: 'pexels' (default \u2014 polished real-world photos & footage) | 'pixabay' (illustrations, vectors, clip-art, icons + more variety; pair with image_type/video_type). music/sfx: 'clueso' (default for music \u2014 our curated, brand-safe, well-described library, best semantic search) | 'freesound' (huge community library; best for niche music and sound effects \u2014 default for sfx). Offering the user music/sfx options \u2192 one call per library, both.",
          "type": "string",
          "enum": [
            "pexels",
            "pixabay",
            "clueso",
            "freesound"
          ]
        },
        "image_type": {
          "description": "stock images + provider='pixabay' only: 'photo' | 'illustration' | 'vector' (flat/drawn graphics, icons, clip-art). Use illustration/vector for graphic looks; photo (or Pexels) for realism.",
          "type": "string",
          "enum": [
            "photo",
            "illustration",
            "vector"
          ]
        },
        "video_type": {
          "description": "stock videos + provider='pixabay' only: 'film' (live footage) | 'animation' (motion graphics).",
          "type": "string",
          "enum": [
            "film",
            "animation"
          ]
        },
        "tier": {
          "description": "animations only: 'org' (this workspace), 'official' (Clueso's) or 'community'. Default all three.",
          "type": "string",
          "enum": [
            "org",
            "official",
            "community"
          ]
        },
        "project_id": {
          "description": "animations: also list this project's own animations, first. icons: search this project's brand kit's icon set.",
          "type": "string"
        },
        "brand_kit_id": {
          "description": "icons only: search this brand kit's icon set (an id from find(type='brand_kits')) instead of the project's.",
          "type": "string"
        },
        "library": {
          "description": "icons only: an icon set id ('lucide', 'ph', 'tabler', ...) other than the kit's. One set per video, so only for a video using no other.",
          "type": "string"
        },
        "tag_match": {
          "description": "projects only, with tags: 'any' (default) = at least one of the tags, 'all' = every one of them.",
          "type": "string",
          "enum": [
            "any",
            "all"
          ]
        },
        "mood": {
          "description": "music/sfx: moods to rank first.",
          "maxItems": 3,
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "uplifting",
              "hopeful",
              "happy",
              "playful",
              "quirky",
              "confident",
              "inspiring",
              "epic",
              "energetic",
              "laid-back",
              "calm",
              "dreamy",
              "warm",
              "sentimental",
              "focused",
              "serious",
              "mysterious",
              "suspenseful",
              "dark",
              "sad"
            ]
          }
        },
        "genre": {
          "description": "music: genres to rank first.",
          "maxItems": 3,
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "corporate",
              "electronic",
              "pop",
              "rock",
              "indie",
              "hip-hop",
              "lo-fi",
              "house",
              "funk",
              "rnb",
              "jazz",
              "acoustic-folk",
              "ambient",
              "cinematic",
              "classical",
              "world"
            ]
          }
        },
        "energy": {
          "description": "music: 1 still, 2 gentle, 3 steady, 4 driving, 5 intense; ranks.",
          "type": "integer",
          "minimum": 1,
          "maximum": 5
        },
        "vocals": {
          "description": "music: false = instrumental only, true = with vocals.",
          "anyOf": [
            {
              "type": "boolean"
            },
            {
              "type": "string",
              "enum": [
                "0",
                "1",
                "true",
                "false",
                "yes",
                "no"
              ]
            }
          ]
        },
        "bpm_min": {
          "description": "music: tempo floor.",
          "type": "integer",
          "minimum": 1,
          "maximum": 400
        },
        "bpm_max": {
          "description": "music: tempo ceiling.",
          "type": "integer",
          "minimum": 1,
          "maximum": 400
        },
        "avoid": {
          "description": "music/sfx: moods or genres to exclude.",
          "maxItems": 6,
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "uplifting",
              "hopeful",
              "happy",
              "playful",
              "quirky",
              "confident",
              "inspiring",
              "epic",
              "energetic",
              "laid-back",
              "calm",
              "dreamy",
              "warm",
              "sentimental",
              "focused",
              "serious",
              "mysterious",
              "suspenseful",
              "dark",
              "sad",
              "corporate",
              "electronic",
              "pop",
              "rock",
              "indie",
              "hip-hop",
              "lo-fi",
              "house",
              "funk",
              "rnb",
              "jazz",
              "acoustic-folk",
              "ambient",
              "cinematic",
              "classical",
              "world"
            ]
          }
        },
        "sfx_type": {
          "description": "sfx: the kind of effect.",
          "type": "string",
          "enum": [
            "whoosh",
            "riser",
            "impact",
            "sub-drop",
            "click",
            "pop",
            "typing",
            "swipe",
            "notification",
            "success",
            "error",
            "glitch",
            "shimmer",
            "camera-shutter",
            "ambience"
          ]
        },
        "similar_to": {
          "description": "music/sfx: an earlier result's id (a Freesound src also works); returns sounds like it. No other filters with it.",
          "type": "string"
        },
        "detail": {
          "description": "voices, music, sfx, fonts, brand_kits, icons: 'compact' (default) \u2014 the essentials to choose and use one (voices: id, name, gender, accent, engine, tags, a short description; music/sfx: id, name, src, duration, bpm, tags, a short description; fonts: id, name; brand_kits: id, name, is_default, accent, title_font, logo_count, icon_library; icons: names); 'full' \u2014 every field (preview urls, thumbnails, attribution, rhythm, full descriptions; for brand_kits every section of the kit; for icons each one's src).",
          "type": "string",
          "enum": [
            "compact",
            "full"
          ]
        },
        "page": {
          "description": "Page (default 1) \u2014 projects/element_components/clueprints.",
          "type": "integer",
          "minimum": 1,
          "maximum": 9007199254740991
        },
        "limit": {
          "description": "Page size, and a cap on every type, stock media included (default 8 for voices, music and sfx, every font for fonts, 50 otherwise; a '_more' item or a note says when more exist).",
          "type": "integer",
          "minimum": 1,
          "maximum": 100
        }
      },
      "required": [
        "type"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "switch_workspace": {
      "type": "object",
      "properties": {
        "workspaceId": {
          "type": "string",
          "description": "Workspace ID to switch to"
        }
      },
      "required": [
        "workspaceId"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "get_project": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "string",
          "description": "The project ID"
        },
        "render": {
          "type": "object",
          "properties": {
            "seams": {
              "description": "Every cut: the last frame before it and the first after, labelled with its transition (HTML animations drawn from their pages on both sides). 7 cuts a call.",
              "anyOf": [
                {
                  "type": "boolean",
                  "const": true
                },
                {
                  "type": "object",
                  "properties": {
                    "from_cut": {
                      "description": "First cut shown (default 0).",
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991
                    }
                  },
                  "additionalProperties": false
                }
              ]
            },
            "clips": {
              "description": "One frame per clip. 15 clips a call.",
              "anyOf": [
                {
                  "type": "boolean",
                  "const": true
                },
                {
                  "type": "object",
                  "properties": {
                    "at": {
                      "description": "Where in each clip (default 'middle'), or clip seconds.",
                      "anyOf": [
                        {
                          "type": "string",
                          "enum": [
                            "start",
                            "middle",
                            "end"
                          ]
                        },
                        {
                          "type": "number",
                          "minimum": 0
                        }
                      ]
                    },
                    "from_clip": {
                      "description": "First clip shown (default 0).",
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991
                    }
                  },
                  "additionalProperties": false
                }
              ]
            },
            "at": {
              "description": "Film seconds, at most 15.",
              "minItems": 1,
              "maxItems": 15,
              "type": "array",
              "items": {
                "type": "number",
                "minimum": 0
              }
            },
            "max_width": {
              "description": "Sheet width in px (default 1280).",
              "type": "integer",
              "minimum": 320,
              "maximum": 1568
            },
            "animations": {
              "description": "false draws every element settled.",
              "type": "boolean"
            }
          },
          "additionalProperties": false,
          "description": "The whole film as ONE labelled image instead of the summary: exactly one of seams, clips or at. A reply that leaves cuts or clips out names the next from_cut or from_clip."
        }
      },
      "required": [
        "project_id"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "get_clip": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "string",
          "description": "The project ID"
        },
        "clip_index": {
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991,
          "description": "Zero-based clip index"
        },
        "render": {
          "description": "Render the clip as an image. Presence of this object IS the request to render \u2014 omit it and nothing is rendered. `{}` renders at t=0. Independent of `select`, which only shapes the JSON: pair `select: []` with `render: {}` for the PNG alone (smallest read). CHECKING YOUR WORK: pass `timestamps` with SEVERAL mid-clip moments, not the t=0 default \u2014 text and image elements have entry animations (a ~0.4s slide/fade by default), so at t=0 they have not arrived yet and a correct edit renders as an empty frame, while shapes have no entry animation and do show at t=0. That mix is what makes a single t=0 render actively misleading: some elements appear and others don't. A list comes back as one grid for about a quarter of the tokens of the same frames separately, so checking several moments is the cheap option, not the expensive one. `animations: false` draws everything settled if you would rather not pick moments at all.",
          "type": "object",
          "properties": {
            "timestamps": {
              "description": "Which moment(s) of the clip to render, in seconds (default 0). Pass several \u2014 [0.5, 2, 4] \u2014 and they come back as one labelled grid costing about a quarter of what those frames cost individually. Prefer several whenever you are checking rather than reading: entry animations mean no single moment shows everything, so one frame is usually the wrong question. A grid cell is a third of the width, so for small print pass a single number. Max 15; duplicates dropped, sorted into time order.",
              "anyOf": [
                {
                  "type": "number",
                  "minimum": 0
                },
                {
                  "minItems": 1,
                  "maxItems": 15,
                  "type": "array",
                  "items": {
                    "type": "number",
                    "minimum": 0
                  }
                }
              ]
            },
            "layout": {
              "description": "Only meaningful when `timestamps` holds several. 'sheet' (default) tiles them into one image; 'separate' returns them full-size, which costs ~4x more \u2014 use it only when a grid cell is too small to judge.",
              "type": "string",
              "enum": [
                "sheet",
                "separate"
              ]
            },
            "save": {
              "description": "Also upload the rendered image to S3 and return presigned_url, which you can pass as source_url to update_clueprint (files=[{path, source_url}]). Uploads ONE image, so it works with a single frame or a grid but not with layout:'separate' \u2014 there is one URL and several frames.",
              "type": "boolean"
            },
            "max_width": {
              "description": "Width in px of the image you get back: default 960 for a single frame, 1280 for a grid (the whole grid; a cell is about a third). An image costs about (w\u00d7h)/750 tokens and nothing is gained above 1568. On a render of one animation it is the resolution drawn before the crop; sheet cells stay at most 360px wide.",
              "type": "integer",
              "minimum": 320,
              "maximum": 1568
            },
            "animations": {
              "description": "Whether entry/exit animations play at this timestamp. Default true \u2014 the frame as the video will show it, which is what you want to judge motion. Pass false to draw every element settled at its final position and full opacity: use that to check COMPOSITION, because with staggered starts and short windows there may be no single timestamp at which nothing is mid-flight, and an element part-way through a slide reads as mis-positioned when it is fine.",
              "type": "boolean"
            },
            "preview": {
              "description": "A preview token from edit_animation(dry_run: true): the HTML animation it was about is drawn from the unsaved page, cropped like element_id -- the element it names, else one using that site (this clip first), else the page alone on this clip. Nothing is saved.",
              "type": "string",
              "minLength": 1
            },
            "element_id": {
              "description": "Render only this element over the clip's background, each moment cropped to its box, as one small sheet: the fast way to check one animation (an HTML animation is drawn from its page, at its own values and timeline). Without timestamps it shows four moments across the element.",
              "type": "string",
              "minLength": 1
            },
            "filmstrip": {
              "anyOf": [
                {
                  "type": "boolean",
                  "const": true
                },
                {
                  "type": "object",
                  "properties": {
                    "from": {
                      "description": "Window start, element-local seconds, or clip seconds for a whole clip (default 0).",
                      "type": "number",
                      "minimum": 0
                    },
                    "to": {
                      "description": "Window end, element-local seconds, or clip seconds for a whole clip (default the end).",
                      "type": "number",
                      "exclusiveMinimum": 0
                    },
                    "around": {
                      "description": "Center the window on a cue ('cue:land') or an action clip ('action:hop', by name or id) instead of from/to. On a whole clip, a cue several animations share is 'cue:land@<element name>'.",
                      "type": "string",
                      "pattern": "^(cue|action):.+"
                    },
                    "span": {
                      "description": "With around: the window's length in seconds (default 2 around a cue; the clip plus 0.5 around an action).",
                      "type": "number",
                      "exclusiveMinimum": 0
                    },
                    "frames": {
                      "description": "How many frames across the window, first and last included (default 12, max 16).",
                      "type": "integer",
                      "minimum": 2,
                      "maximum": 16
                    },
                    "fps": {
                      "description": "Frames per second instead of a count, e.g. 8 over 2 s is 17, capped at 16.",
                      "type": "number",
                      "exclusiveMinimum": 0,
                      "maximum": 60
                    },
                    "seam": {
                      "description": "get_clip of a whole clip only: the cut from this clip into the next, as the player shows it: this clip's last second, its transition, and the next clip's first second, in this clip's seconds; takes frames, no window. Every cut of the film at once: get_project({render: {seams: true}}).",
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": false
                }
              ],
              "description": "See motion in ONE image instead of four moments: frames spaced evenly across a window (default 12 over the whole animation), in reading order, each labelled with its element-local time, with a thin timeline under each row marking the frames, cues (lines) and action clips (bars). Read pacing, easing and whether a beat lands on its cue from it. true for the defaults, or {from, to} / {around: 'cue:<key>' | 'action:<name>', span}, with frames or fps. Replaces timestamps. On get_clip without element_id or preview it shows the whole clip, every element drawn, in clip seconds, and the timeline also ticks each element's start and end. HTML animations (pages) are drawn from their page in every render, the next clip's across a seam included; their cues and actions are the page's."
            },
            "video": {
              "description": "For people: also a short low-resolution MP4 of the same window (the filmstrip's, or the animation's first 10 s), returned as video_url, a link valid for an hour. You can't watch it: judge motion from the filmstrip.",
              "type": "boolean"
            },
            "test": {
              "type": "object",
              "properties": {
                "parameter_values": {
                  "description": "Values to try, over the element's own (an image or video value: a URL or upload).",
                  "type": "object",
                  "propertyNames": {
                    "type": "string"
                  },
                  "additionalProperties": {}
                },
                "param_tracks": {
                  "description": "Keyframes to try for animatable parameters: {key: [{t, value, interpolation?}]}.",
                  "type": "object",
                  "propertyNames": {
                    "type": "string"
                  },
                  "additionalProperties": {
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "t": {
                          "type": "number",
                          "minimum": 0,
                          "description": "Element-local (page) seconds."
                        },
                        "value": {},
                        "interpolation": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "t",
                        "value"
                      ],
                      "additionalProperties": {}
                    }
                  }
                },
                "actions": {
                  "description": "Action clips to try instead of the element's: [{name, start, duration?, args?}].",
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string",
                        "minLength": 1
                      },
                      "start": {
                        "type": "number",
                        "minimum": 0
                      },
                      "duration": {
                        "type": "number",
                        "exclusiveMinimum": 0
                      },
                      "args": {
                        "type": "object",
                        "propertyNames": {
                          "type": "string"
                        },
                        "additionalProperties": {}
                      }
                    },
                    "required": [
                      "name",
                      "start"
                    ],
                    "additionalProperties": false
                  }
                },
                "cues": {
                  "description": "Cue times to try: {key: seconds}.",
                  "type": "object",
                  "propertyNames": {
                    "type": "string"
                  },
                  "additionalProperties": {
                    "type": "number",
                    "minimum": 0
                  }
                }
              },
              "additionalProperties": false,
              "description": "Try other settings on the one animation rendered (element_id or preview), nothing saved: parameter_values, param_tracks, actions, cues, in element-local (page) seconds. For an HTML animation the page is drawn with them; check a value or a cue before setting it with update_elements."
            }
          },
          "additionalProperties": false
        },
        "select": {
          "description": "Ask for exactly the JSON you want, GraphQL-style. Omit for everything; pass [] for none.\n  Sections: 'elements' (whole element rows), 'voiceover_words' (per-word VO timings; returns the key of the same name, holding `{word, start, end, punctuated_word}` with start/end in SECONDS \u2014 null on a clip with no transcription), 'groups' (group rows: id, name, parent_id, bounds_px, anchor_px, keyframes), 'busy' (generations still writing to this clip, as `[{entity_path, job_type}]` \u2014 EMPTY means nothing is pending, which is how you know a voiceover or AI image has landed; it is the same lock that would refuse your write, so a non-empty list also tells you what not to touch yet). Each section returns the key it is named after. Rendering is `render`, not a value here.\n  Per-key: 'elements.<key>' projects element rows to just those keys (id is always kept). Keys: name, element_type, x, y, width, height, start_time, end_time, rotation, keyframes, textdata, shapedata, imagedata, videodata, zoomdata, codedata, clickaction, questiondata, resultsdata, formdata, avatardata, parent_id, dropShadows, animation. y_top is text-only and present ONLY when alignment is 'center' \u2014 the unambiguous TOP edge, which is exactly the case where `y` is NOT the top but the vertical CENTRE. To put that position back, send it as `y` with y_anchor:'top'.\n  Examples: ['elements.x','elements.y','elements.width','elements.height'] to read geometry; ['elements.name','elements.start_time','elements.end_time'] for a timing pass; ['voiceover_words'] to sync visuals to narration; ['groups'] to resolve a group id for update_groups; [] with render:{} returns the PNG with no JSON (smallest read); ['elements.textdata','voiceover_words'] to rewrite copy against the VO.\n  Mixing 'elements' with 'elements.<key>' returns whole rows. Use element_ids to choose WHICH rows \u2014 that is independent of this.",
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "elements",
              "voiceover_words",
              "groups",
              "busy",
              "geometry",
              "elements.name",
              "elements.element_type",
              "elements.x",
              "elements.y",
              "elements.width",
              "elements.height",
              "elements.start_time",
              "elements.end_time",
              "elements.rotation",
              "elements.keyframes",
              "elements.textdata",
              "elements.shapedata",
              "elements.imagedata",
              "elements.videodata",
              "elements.zoomdata",
              "elements.codedata",
              "elements.clickaction",
              "elements.questiondata",
              "elements.resultsdata",
              "elements.formdata",
              "elements.avatardata",
              "elements.parent_id",
              "elements.dropShadows",
              "elements.animation"
            ]
          }
        },
        "element_ids": {
          "description": "WHICH element rows to return \u2014 all others are dropped, and [] returns none. Independent of `select`, which chooses the sections/keys. Use it to re-inspect just what you added or updated; most add_elements/update_elements already echo the element's resolved layout, so often you don't need this at all.",
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      },
      "required": [
        "project_id",
        "clip_index"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "update_clips": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "string",
          "description": "Project ID"
        },
        "updates": {
          "minItems": 1,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "clip_index": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991,
                "description": "Zero-based clip index"
              },
              "duration": {
                "description": "New duration (seconds)",
                "type": "number",
                "exclusiveMinimum": 0
              },
              "title": {
                "description": "New clip title",
                "type": "string"
              },
              "background_color": {
                "description": "Hex background color (keeps pattern, changes color)",
                "type": "string"
              },
              "background": {
                "description": "Full background object from find(type='backgrounds'), or { src, media_type?, color?, padding?, border_radius?, shadow?, volume? } for any image/video URL or upload id.",
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {}
              },
              "transition": {
                "description": "Transition at the END of this clip, into the next one; null to remove",
                "anyOf": [
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "circleWipe",
                          "rainbowWipe",
                          "cornerWipe",
                          "stack",
                          "colorWipe",
                          "dissolve",
                          "pushPull",
                          "fadeToColor",
                          "flip"
                        ],
                        "description": "Transition style. NOTE: a clip's transition plays at the END of that clip, into the next one \u2014 to put a transition between clips 2 and 3, set it on clip 2."
                      },
                      "duration": {
                        "type": "number",
                        "exclusiveMinimum": 0,
                        "description": "Transition length in seconds."
                      },
                      "direction": {
                        "description": "Direction, honoured only by some types: rainbowWipe and cornerWipe take topLeft/topRight/bottomLeft/bottomRight (default topRight); colorWipe takes left or right (default right); stack and pushPull take left/right/top/bottom (default right); flip takes horizontalLeft/horizontalCenter/horizontalRight/verticalTop/verticalCenter/verticalBottom (default horizontalCenter) \u2014 the axis it turns about (horizontal = left\u2194right, vertical = up\u2194down) and the line it turns on (an edge, or the middle for a card flip). Ignored by dissolve, circleWipe and fadeToColor.",
                        "type": "string"
                      },
                      "color": {
                        "description": "Hex color, honoured by fadeToColor (default #000000), colorWipe (default #073cad), rainbowWipe (default #D43F8C) and flip (the backdrop behind the turning card, default #000000).",
                        "type": "string"
                      },
                      "bezier": {
                        "description": "Custom easing of the transition, CSS cubic-bezier [x1, y1, x2, y2], e.g. [0.34, 1.56, 0.64, 1] for a slight bounce. Honoured by flip; omit for its default ease-in-out.",
                        "minItems": 4,
                        "maxItems": 4,
                        "type": "array",
                        "items": {
                          "type": "number"
                        }
                      }
                    },
                    "required": [
                      "type",
                      "duration"
                    ]
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "hidden": {
                "description": "Whether the clip is hidden",
                "anyOf": [
                  {
                    "type": "boolean"
                  },
                  {
                    "type": "string",
                    "enum": [
                      "0",
                      "1",
                      "true",
                      "false",
                      "yes",
                      "no"
                    ]
                  }
                ]
              },
              "position": {
                "description": "Move clip to this zero-based index",
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              "subtitle_settings": {
                "description": "Captions on this clip: {showSubtitles?, size?, style?, subtitlesStyle?}.",
                "type": "object",
                "properties": {
                  "showSubtitles": {
                    "type": "boolean"
                  },
                  "size": {
                    "type": "integer",
                    "exclusiveMinimum": 0,
                    "maximum": 9007199254740991
                  },
                  "style": {
                    "description": "A caption library style id, e.g. 'box_karaoke'; subtitlesStyle fields override it.",
                    "type": "string"
                  },
                  "subtitlesStyle": {
                    "type": "object",
                    "propertyNames": {
                      "type": "string"
                    },
                    "additionalProperties": {}
                  }
                },
                "additionalProperties": {}
              },
              "voiceover_volume": {
                "description": "Narration loudness for this clip, 0-100 (default 100). Set 0 for a silent cut \u2014 this MUTES the voiceover without deleting it, so the text and its word timings survive and you can restore it by setting the volume back. No-op on a clip that has no voiceover.",
                "type": "number",
                "minimum": 0,
                "maximum": 100
              },
              "video_styling": {
                "description": "VIDEO CLIPS ONLY: reposition/resize the screen recording on the canvas (e.g. into a designed window slot). position/size are canvas fractions 0\u20131; [1,1] = full-bleed. Fields merge onto the clip's existing video_styling. Ignored for slide clips.",
                "type": "object",
                "properties": {
                  "position": {
                    "description": "Footage center [x,y] as canvas fractions 0\u20131 (0.5,0.5 = centered).",
                    "minItems": 2,
                    "maxItems": 2,
                    "type": "array",
                    "items": {
                      "type": "number"
                    }
                  },
                  "size": {
                    "description": "Footage [width,height] as canvas fractions 0\u20131 ([1,1] = full-bleed).",
                    "minItems": 2,
                    "maxItems": 2,
                    "type": "array",
                    "items": {
                      "type": "number"
                    }
                  },
                  "crop": {
                    "description": "Footage crop rect in fractional coords (0\u20131), or null to clear.",
                    "anyOf": [
                      {
                        "type": "object",
                        "properties": {
                          "position": {
                            "minItems": 2,
                            "maxItems": 2,
                            "type": "array",
                            "items": {
                              "type": "number"
                            },
                            "description": "[left, top] as fractions of natural size (0\u20131)."
                          },
                          "size": {
                            "minItems": 2,
                            "maxItems": 2,
                            "type": "array",
                            "items": {
                              "type": "number"
                            },
                            "description": "[width, height] as fractions of natural size (0\u20131)."
                          }
                        },
                        "required": [
                          "position",
                          "size"
                        ]
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "border": {
                    "description": "Frame around the footage ({type?, color?, borderWidth?, stroke?} \u2014 border_width also accepted), or null to clear.",
                    "anyOf": [
                      {
                        "type": "object",
                        "properties": {
                          "type": {
                            "description": "Frame style. omnibox = browser toolbar with URL bar over the footage; browser = window chrome; notch = phone notch; solid = plain stroke; glass; animated; none = remove. Omit to restyle the current border.",
                            "type": "string",
                            "enum": [
                              "none",
                              "solid",
                              "browser",
                              "omnibox",
                              "notch",
                              "glass",
                              "animated"
                            ]
                          },
                          "color": {
                            "description": "Frame color hex. Defaults per type: #212528 for omnibox/browser, #000000 for notch, #FFFFFF for glass, brand blue otherwise.",
                            "type": "string"
                          },
                          "borderWidth": {
                            "description": "Border width in pixels.",
                            "type": "number",
                            "minimum": 0
                          },
                          "border_width": {
                            "description": "Snake-case alias for borderWidth. Border width in pixels.",
                            "type": "number",
                            "minimum": 0
                          },
                          "stroke": {
                            "description": "SOLID borders only: stroke alignment and dashed style.",
                            "type": "object",
                            "properties": {
                              "align": {
                                "type": "string",
                                "enum": [
                                  "inside",
                                  "center",
                                  "outside"
                                ]
                              },
                              "style": {
                                "type": "string",
                                "enum": [
                                  "solid",
                                  "dashed"
                                ]
                              },
                              "dash": {
                                "description": "Dash length px (dashed).",
                                "type": "number",
                                "minimum": 0
                              },
                              "gap": {
                                "description": "Gap length px (dashed).",
                                "type": "number",
                                "minimum": 0
                              }
                            }
                          }
                        }
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                }
              },
              "after_clip": {
                "description": "Interactivity beta: where playback goes when this clip ends; null resets to next_clip.",
                "anyOf": [
                  {
                    "type": "object",
                    "properties": {
                      "mode": {
                        "type": "string",
                        "enum": [
                          "next_clip",
                          "end_video",
                          "jump_clip"
                        ],
                        "description": "next_clip (the default when unset), end_video (marks completion of a non-quiz interactive video), or jump_clip."
                      },
                      "target_clip_id": {
                        "description": "jump_clip only \u2014 the clip_id to go to.",
                        "type": "string"
                      }
                    },
                    "required": [
                      "mode"
                    ],
                    "description": "Where playback goes when this clip ends. Ignored while a wait-for-click button or branch menu on the clip is unresolved \u2014 the component decides."
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            },
            "required": [
              "clip_index"
            ]
          },
          "description": "One entry per clip to update."
        }
      },
      "required": [
        "project_id",
        "updates"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "remove_clip": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "string",
          "description": "The project ID"
        },
        "clip_index": {
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991,
          "description": "Zero-based clip index to remove"
        }
      },
      "required": [
        "project_id",
        "clip_index"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "split_clip": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "string",
          "description": "The project ID"
        },
        "clip_index": {
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991,
          "description": "Index of the clip to split"
        },
        "split_time": {
          "type": "number",
          "exclusiveMinimum": 0.1,
          "description": "Time in seconds within the clip where to split (clip-relative, not timeline-relative). Must be > 0.1s and < clip_duration \u2212 0.1s; values outside this range are rejected."
        }
      },
      "required": [
        "project_id",
        "clip_index",
        "split_time"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "captions": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "string",
          "description": "The project ID"
        },
        "action": {
          "type": "string",
          "enum": [
            "list",
            "edit",
            "reset",
            "split",
            "merge",
            "timing",
            "timing_reset",
            "manual_add",
            "manual_edit",
            "manual_remove",
            "translate",
            "language"
          ],
          "description": "What to do \u2014 see the tool description"
        },
        "clip_index": {
          "description": "The clip. Required for every action except translate/language, where omitting it means every clip.",
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991
        },
        "start_word_index": {
          "description": "edit/reset/split/merge/timing/timing_reset \u2014 the caption, exactly as list returned it.",
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991
        },
        "split_word_index": {
          "description": "split \u2014 the word that starts the new second caption.",
          "type": "integer",
          "minimum": 1,
          "maximum": 9007199254740991
        },
        "text": {
          "description": "edit / manual_add / manual_edit \u2014 the caption text.",
          "type": "string"
        },
        "start_time": {
          "description": "timing / manual_add / manual_edit \u2014 seconds from the start of the clip.",
          "type": "number",
          "minimum": 0
        },
        "end_time": {
          "description": "timing / manual_add / manual_edit \u2014 seconds from the start of the clip.",
          "type": "number",
          "minimum": 0
        },
        "caption_id": {
          "description": "manual_edit / manual_remove \u2014 from list's manual_captions.",
          "type": "string"
        },
        "language": {
          "description": "translate \u2014 language id (\"fr\") or name (\"French\"). language \u2014 a translated id, or \"original\".",
          "type": "string"
        }
      },
      "required": [
        "project_id",
        "action"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "duplicate_clip": {
      "type": "object",
      "properties": {
        "target_project_id": {
          "type": "string",
          "description": "Project (guide) ID to insert the duplicate into."
        },
        "source_clip_id": {
          "type": "string",
          "description": "ID of the clip to duplicate."
        },
        "source_project_id": {
          "description": "Cross-project mode: ID of the project the source clip lives in. Omit for same-project duplication.",
          "type": "string"
        },
        "after_clip_id": {
          "description": "Insert after this clip ID in the target. Appends at end if omitted.",
          "type": "string"
        }
      },
      "required": [
        "target_project_id",
        "source_clip_id"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "add_clips": {
      "type": "object",
      "properties": {
        "kind": {
          "type": "string",
          "enum": [
            "blank",
            "pptx",
            "video",
            "template"
          ],
          "description": "Source for the new clips: 'blank' | 'pptx' | 'video' | 'template'."
        },
        "project_id": {
          "type": "string",
          "description": "Project (guide) ID."
        },
        "after_clip_id": {
          "description": "Insert after this clip ID; appends at end if omitted. kind='video': '' inserts at the front.",
          "type": "string"
        },
        "clips": {
          "description": "kind='blank' only: one entry per blank clip. Optionally set background_color/background + transition per clip at creation \u2014 no follow-up update_clips needed.",
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "title": {
                "description": "Clip title shown in the timeline.",
                "type": "string"
              },
              "duration": {
                "description": "Clip length in seconds. Defaults to a short blank-clip duration.",
                "type": "number",
                "exclusiveMinimum": 0
              },
              "background_color": {
                "description": "Hex background set at creation, e.g. '#0B0E1A'.",
                "type": "string"
              },
              "background": {
                "description": "Background set at creation: an object from find(type='backgrounds'), or { src, media_type?, padding?, border_radius?, shadow?, volume? } as in update_clips.",
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {}
              },
              "transition": {
                "description": "Transition at the END of this clip, into the next one.",
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "circleWipe",
                      "rainbowWipe",
                      "cornerWipe",
                      "stack",
                      "colorWipe",
                      "dissolve",
                      "pushPull",
                      "fadeToColor",
                      "flip"
                    ],
                    "description": "Transition style. NOTE: a clip's transition plays at the END of that clip, into the next one \u2014 to put a transition between clips 2 and 3, set it on clip 2."
                  },
                  "duration": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "description": "Transition length in seconds."
                  },
                  "direction": {
                    "description": "Direction, honoured only by some types: rainbowWipe and cornerWipe take topLeft/topRight/bottomLeft/bottomRight (default topRight); colorWipe takes left or right (default right); stack and pushPull take left/right/top/bottom (default right); flip takes horizontalLeft/horizontalCenter/horizontalRight/verticalTop/verticalCenter/verticalBottom (default horizontalCenter) \u2014 the axis it turns about (horizontal = left\u2194right, vertical = up\u2194down) and the line it turns on (an edge, or the middle for a card flip). Ignored by dissolve, circleWipe and fadeToColor.",
                    "type": "string"
                  },
                  "color": {
                    "description": "Hex color, honoured by fadeToColor (default #000000), colorWipe (default #073cad), rainbowWipe (default #D43F8C) and flip (the backdrop behind the turning card, default #000000).",
                    "type": "string"
                  },
                  "bezier": {
                    "description": "Custom easing of the transition, CSS cubic-bezier [x1, y1, x2, y2], e.g. [0.34, 1.56, 0.64, 1] for a slight bounce. Honoured by flip; omit for its default ease-in-out.",
                    "minItems": 4,
                    "maxItems": 4,
                    "type": "array",
                    "items": {
                      "type": "number"
                    }
                  }
                },
                "required": [
                  "type",
                  "duration"
                ]
              }
            }
          }
        },
        "mcp_upload_id": {
          "description": "kind='pptx' or 'video': mcp_upload_id from upload_file, or a Clueso upload_id (or its S3 key).",
          "type": "string"
        },
        "slide_indices": {
          "description": "kind='pptx' only: 0-based slide indices to insert. Omit to insert all slides.",
          "type": "array",
          "items": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          }
        },
        "mime_type": {
          "description": "kind='video' only: MIME type (default 'video/mp4'). Audio MIME types insert the file as a sequential clip with auto-TTS; for background music, use add_audio instead.",
          "type": "string"
        },
        "title": {
          "description": "kind='video' only: clip title (default 'Video').",
          "type": "string"
        },
        "voiceover_text": {
          "description": "kind='video' only: post-AI voiceover script (saved as v2). Original audio is always retained as the base transcript. Bracketed emotion tags ([excited], [whispers]) are performed, not spoken; <pause:1.5s> is exact timed silence.",
          "type": "string"
        },
        "cuts": {
          "description": "kind='video' only: per-clip cuts. Each entry becomes one inserted clip. Omit to insert the full upload as a single clip.",
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "start_seconds": {
                "type": "number",
                "minimum": 0,
                "description": "Cut start in original-source seconds (inclusive)."
              },
              "end_seconds": {
                "type": "number",
                "exclusiveMinimum": 0,
                "description": "Cut end in original-source seconds (exclusive)."
              },
              "heading": {
                "description": "Optional clip title for this cut.",
                "type": "string"
              }
            },
            "required": [
              "start_seconds",
              "end_seconds"
            ]
          }
        },
        "source_start_clip_id": {
          "description": "kind='video' re-cut: first clip of the run to re-cut (inclusive).",
          "type": "string"
        },
        "source_end_clip_id": {
          "description": "kind='video' re-cut: last clip of the run (inclusive).",
          "type": "string"
        },
        "auto_elements": {
          "description": "kind='video' only: when true, run auto-element detection (zooms, spotlights, blurs). Default false. Only meaningful on cache miss; cache hits clone the originating run's auto-elements.",
          "type": "boolean"
        },
        "template_id": {
          "description": "kind='template' only: template_id from find(type='clip_templates').",
          "type": "string"
        },
        "text_overrides": {
          "description": "kind='template' only: text for the template's text elements, in document order.",
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      },
      "required": [
        "kind",
        "project_id"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "get_element_schema": {
      "type": "object",
      "properties": {
        "element_type": {
          "type": "string",
          "enum": [
            "text",
            "callout",
            "rectangle",
            "arrow",
            "blur",
            "spotlight",
            "zoom",
            "image",
            "video",
            "animation",
            "question",
            "results",
            "form",
            "avatar",
            "group"
          ],
          "description": "One of: text, callout, rectangle, arrow, blur, spotlight, zoom, image, video, animation, question, results, form, avatar, group"
        },
        "mode": {
          "default": "add",
          "description": "'add' returns required + optional fields; 'update' returns all-optional.",
          "type": "string",
          "enum": [
            "add",
            "update"
          ]
        },
        "fields": {
          "description": "Return only these fields, with their full notes, default and an example \u2014 text is 15KB whole, ~300 bytes for two.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "query": {
          "description": "Words describing the property you want ('pill background', 'corner radius', 'line height'): returns the best-matching fields (up to 5) as `fields` would.",
          "type": "string",
          "minLength": 2,
          "maxLength": 80
        },
        "detail": {
          "description": "'compact' (default): names, types and one-line notes. 'full': every field's full notes, nested docs and the keyframe rules.",
          "type": "string",
          "enum": [
            "compact",
            "full"
          ]
        },
        "format": {
          "description": "Defaults to 'declarations' \u2014 TypeScript-style, which is the better READ. Inside run_script it defaults to 'json' instead, because a script indexes into the structure and the declaration text would arrive newline-escaped. Set it explicitly to override either default.",
          "type": "string",
          "enum": [
            "declarations",
            "json"
          ]
        }
      },
      "required": [
        "element_type"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "add_elements": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "string",
          "description": "Project ID"
        },
        "elements": {
          "minItems": 1,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "clip_index": {
                "description": "Zero-based clip index. Required unless hoisted into `defaults`.",
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              "element_type": {
                "description": "One of: text, callout, rectangle, arrow, blur, spotlight, zoom, image, video, animation, question, results, form, avatar. Omit when using component_id.",
                "type": "string",
                "enum": [
                  "text",
                  "callout",
                  "rectangle",
                  "arrow",
                  "blur",
                  "spotlight",
                  "zoom",
                  "image",
                  "video",
                  "animation",
                  "question",
                  "results",
                  "form",
                  "avatar"
                ]
              },
              "component_id": {
                "description": "Insert a SAVED COMPONENT exactly as stored \u2014 no AI generation, no restyling. This is how you reuse a component from your workspace verbatim (find one with find(type='element_components')). Components are workspace-scoped \u2014 there is no community library for them. A component is a fragment of a clip \u2014 one or more elements, possibly grouped \u2014 so this inserts ALL of them and returns every new element_id plus the animation's parameter_schema. Customise afterwards with update_elements(type_data.parameter_values). Mutually exclusive with element_type/type_data. To instead GENERATE a new animation using a component as a style reference, use element_type='animation' with type_data.base_component_id.",
                "type": "string",
                "minLength": 1
              },
              "group": {
                "description": "Put this element in a named group. The name is the handle: items in ONE call sharing the same name land in the SAME group, and a later call with that name adds to it. Use it to keep a unit together (a card and its label, a stat and its caption) so the user can move or hide it as one thing. Grouping never moves anything \u2014 coordinates stay absolute. Animations generated from a `prompt` and AI-generated images are made in the background and cannot be grouped as they are added; add them first, then set `group` via update_elements. A placed animation (`animation` + `entry`, or `code`) takes `group` and `insert_at` here like any element, and lands below later items of the same call.",
                "type": "string",
                "minLength": 1
              },
              "insert_at": {
                "description": "Where the element lands in z-order, so you don't need a follow-up reorder_elements. `front` (default) puts it on top of what's already on the clip; `back` puts it behind everything \u2014 what a backdrop panel or a wash needs. `{before: element_id}` / `{after: element_id}` place it next to a specific element or group and put it in THAT node's group, which is the only way to position inside a group. Passing `group` as well changes what `front`/`back` address \u2014 see the Z-order notes in this tool's description. Placement is relative to the clip AS IT STANDS when the item is applied, so several `back` items in one call end up in REVERSE input order: send one backdrop per call, or list them back-to-front.",
                "anyOf": [
                  {
                    "type": "string",
                    "enum": [
                      "front",
                      "back"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "before": {
                        "type": "string",
                        "minLength": 1
                      }
                    },
                    "required": [
                      "before"
                    ],
                    "additionalProperties": false
                  },
                  {
                    "type": "object",
                    "properties": {
                      "after": {
                        "type": "string",
                        "minLength": 1
                      }
                    },
                    "required": [
                      "after"
                    ],
                    "additionalProperties": false
                  },
                  {
                    "type": "string",
                    "pattern": "^(before|after):.+$"
                  }
                ]
              },
              "x": {
                "description": "X position in canvas pixels. `x` is the left edge, except TEXT where alignment picks it: left\u2192left, center\u2192centre, right\u2192right (callout, rectangle, arrow, blur, spotlight, image and animation all use the left edge). For zoom, use type_data.centerX instead.",
                "type": "number"
              },
              "y": {
                "description": "Y position in canvas pixels \u2014 for TEXT `y` is the vertical CENTRE when centre-aligned and the TOP otherwise; every other type uses the top. Pair it with y_anchor (below), always.",
                "type": "number"
              },
              "y_anchor": {
                "description": "TEXT only. ALWAYS send `y_anchor` ('top'|'center'|'bottom') when you set `y` on text, so the position states what you meant instead of inheriting the alignment origin. To centre in a row, pill, chip, card or button: pass the container's centre line as `y` with y_anchor:'center'. The server centres the MEASURED box \u2014 you cannot compute that height, since wrapping and `fit` change it. A write-time DIRECTIVE: never stored, never returned by a read, so send it on every call whose `y` means the same thing. (Also accepted inside type_data \u2014 older spelling; this one wins.)",
                "type": "string",
                "enum": [
                  "top",
                  "center",
                  "bottom"
                ]
              },
              "width": {
                "description": "Width, pixels (0 allowed \u2014 e.g. a bar that draws in from 0). For text this is the WRAP width and it is kept as sent: the text wraps to it and the box stays this wide. (Height differs \u2014 the box's height follows the measured text.)",
                "type": "number",
                "minimum": 0
              },
              "height": {
                "description": "Height, pixels (0 allowed). For text the box's height always follows the measured text, so this reads as the space you are giving it: the text is centred in that space and layout.fits_height says whether it fit. Use type_data.fit to shrink the font into it.",
                "type": "number",
                "minimum": 0
              },
              "start_time": {
                "description": "Appear at (seconds)",
                "type": "number",
                "minimum": 0
              },
              "end_time": {
                "description": "Disappear at (seconds)",
                "type": "number",
                "minimum": 0
              },
              "name": {
                "description": "Label",
                "type": "string"
              },
              "rotation": {
                "description": "Rotation in degrees",
                "type": "number"
              },
              "keyframes": {
                "description": "Animation keyframes (\u22652 entries). Each entry has a timestamp plus one or more keyframable properties (position/size in canvas pixels; rotation\u00b0, opacity 0-100, scale, colors as #rrggbb[aa], etc. in native units). Easing: per-entry `interpolation` + optional per-property `interpolation_overrides`. Call get_element_schema to see which properties this element_type can keyframe. null is accepted and means none.",
                "anyOf": [
                  {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "timestamp": {
                          "type": "number",
                          "description": "Clip-relative time in seconds, 0 or later (0 = the clip's first frame)"
                        },
                        "positionX": {
                          "description": "X position in canvas pixels (NOT `x`). Same origin as the element's top-level x: `x` is the left edge, except TEXT where alignment picks it: left\u2192left, center\u2192centre, right\u2192right.",
                          "type": "number"
                        },
                        "positionY": {
                          "description": "Y position in canvas pixels (NOT `y`). On TEXT whose item also sends y with y_anchor, positionY means the same point y does (e.g. y_anchor:'center' \u2192 the line the text is centred on), so a tween from y+40 to y slides it onto that line: the server shifts the track by the offset it applied to y and says so in keyframes_shifted. Otherwise the element's stored origin: for TEXT `y` is the vertical CENTRE when centre-aligned and the TOP otherwise; every other type uses the top.",
                          "type": "number"
                        },
                        "width": {
                          "description": "Width in canvas pixels (0 allowed \u2014 e.g. a bar that draws in from 0)",
                          "type": "number",
                          "minimum": 0
                        },
                        "height": {
                          "description": "Height in canvas pixels (0 allowed)",
                          "type": "number",
                          "minimum": 0
                        },
                        "rotation": {
                          "description": "Rotation in degrees",
                          "type": "number"
                        },
                        "scaleX": {
                          "description": "Horizontal scale multiplier (1 = 100%)",
                          "type": "number"
                        },
                        "scaleY": {
                          "description": "Vertical scale multiplier (1 = 100%)",
                          "type": "number"
                        },
                        "rotateX": {
                          "description": "rotateX (native unit)",
                          "type": "number"
                        },
                        "rotateY": {
                          "description": "rotateY (native unit)",
                          "type": "number"
                        },
                        "cropX": {
                          "description": "cropX (native unit)",
                          "type": "number"
                        },
                        "cropY": {
                          "description": "cropY (native unit)",
                          "type": "number"
                        },
                        "cropWidth": {
                          "description": "cropWidth (native unit)",
                          "type": "number"
                        },
                        "cropHeight": {
                          "description": "cropHeight (native unit)",
                          "type": "number"
                        },
                        "opacity": {
                          "description": "Opacity 0-100",
                          "type": "number"
                        },
                        "blur": {
                          "description": "Layer blur",
                          "type": "number"
                        },
                        "backgroundBlur": {
                          "description": "Backdrop blur behind the element",
                          "type": "number"
                        },
                        "strokeWidth": {
                          "description": "strokeWidth in canvas pixels",
                          "type": "number"
                        },
                        "strokeDash": {
                          "description": "strokeDash in canvas pixels",
                          "type": "number"
                        },
                        "strokeGap": {
                          "description": "strokeGap in canvas pixels",
                          "type": "number"
                        },
                        "strokeTrimStart": {
                          "description": "strokeTrimStart (native unit)",
                          "type": "number"
                        },
                        "strokeTrimEnd": {
                          "description": "strokeTrimEnd (native unit)",
                          "type": "number"
                        },
                        "strokeTrimOffset": {
                          "description": "strokeTrimOffset (native unit)",
                          "type": "number"
                        },
                        "textStrokeWidth": {
                          "description": "textStrokeWidth in canvas pixels",
                          "type": "number"
                        },
                        "textStrokeDash": {
                          "description": "textStrokeDash in canvas pixels",
                          "type": "number"
                        },
                        "textStrokeGap": {
                          "description": "textStrokeGap in canvas pixels",
                          "type": "number"
                        },
                        "imageBorderWidth": {
                          "description": "imageBorderWidth in canvas pixels",
                          "type": "number"
                        },
                        "shadowOffsetX": {
                          "description": "shadowOffsetX in canvas pixels",
                          "type": "number"
                        },
                        "shadowOffsetY": {
                          "description": "shadowOffsetY in canvas pixels",
                          "type": "number"
                        },
                        "shadowBlur": {
                          "description": "shadowBlur in canvas pixels",
                          "type": "number"
                        },
                        "shadowSpread": {
                          "description": "shadowSpread in canvas pixels",
                          "type": "number"
                        },
                        "borderRadiusTopLeft": {
                          "description": "borderRadiusTopLeft in canvas pixels",
                          "type": "number"
                        },
                        "borderRadiusTopRight": {
                          "description": "borderRadiusTopRight in canvas pixels",
                          "type": "number"
                        },
                        "borderRadiusBottomRight": {
                          "description": "borderRadiusBottomRight in canvas pixels",
                          "type": "number"
                        },
                        "borderRadiusBottomLeft": {
                          "description": "borderRadiusBottomLeft in canvas pixels",
                          "type": "number"
                        },
                        "imageCornerRadiusTopLeft": {
                          "description": "imageCornerRadiusTopLeft in canvas pixels",
                          "type": "number"
                        },
                        "imageCornerRadiusTopRight": {
                          "description": "imageCornerRadiusTopRight in canvas pixels",
                          "type": "number"
                        },
                        "imageCornerRadiusBottomRight": {
                          "description": "imageCornerRadiusBottomRight in canvas pixels",
                          "type": "number"
                        },
                        "imageCornerRadiusBottomLeft": {
                          "description": "imageCornerRadiusBottomLeft in canvas pixels",
                          "type": "number"
                        },
                        "videoCornerRadiusTopLeft": {
                          "description": "videoCornerRadiusTopLeft in canvas pixels",
                          "type": "number"
                        },
                        "videoCornerRadiusTopRight": {
                          "description": "videoCornerRadiusTopRight in canvas pixels",
                          "type": "number"
                        },
                        "videoCornerRadiusBottomRight": {
                          "description": "videoCornerRadiusBottomRight in canvas pixels",
                          "type": "number"
                        },
                        "videoCornerRadiusBottomLeft": {
                          "description": "videoCornerRadiusBottomLeft in canvas pixels",
                          "type": "number"
                        },
                        "fontSize": {
                          "description": "Font size in CANVAS PIXELS (same unit as the top-level fontSize \u2014 NOT a fraction).",
                          "type": "number"
                        },
                        "lineHeight": {
                          "description": "lineHeight (native unit)",
                          "type": "number"
                        },
                        "letterSpacing": {
                          "description": "Letter spacing in canvas pixels.",
                          "type": "number"
                        },
                        "paddingTop": {
                          "description": "paddingTop in canvas pixels",
                          "type": "number"
                        },
                        "paddingRight": {
                          "description": "paddingRight in canvas pixels",
                          "type": "number"
                        },
                        "paddingBottom": {
                          "description": "paddingBottom in canvas pixels",
                          "type": "number"
                        },
                        "paddingLeft": {
                          "description": "paddingLeft in canvas pixels",
                          "type": "number"
                        },
                        "textBgRadiusTopLeft": {
                          "description": "textBgRadiusTopLeft in canvas pixels",
                          "type": "number"
                        },
                        "textBgRadiusTopRight": {
                          "description": "textBgRadiusTopRight in canvas pixels",
                          "type": "number"
                        },
                        "textBgRadiusBottomRight": {
                          "description": "textBgRadiusBottomRight in canvas pixels",
                          "type": "number"
                        },
                        "textBgRadiusBottomLeft": {
                          "description": "textBgRadiusBottomLeft in canvas pixels",
                          "type": "number"
                        },
                        "fillColor": {
                          "description": "fillColor as #rrggbb or #rrggbbaa (alpha = opacity)",
                          "type": "string",
                          "pattern": "^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"
                        },
                        "strokeColor": {
                          "description": "strokeColor as #rrggbb or #rrggbbaa (alpha = opacity)",
                          "type": "string",
                          "pattern": "^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"
                        },
                        "textStrokeColor": {
                          "description": "textStrokeColor as #rrggbb or #rrggbbaa (alpha = opacity)",
                          "type": "string",
                          "pattern": "^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"
                        },
                        "imageBorderColor": {
                          "description": "imageBorderColor as #rrggbb or #rrggbbaa (alpha = opacity)",
                          "type": "string",
                          "pattern": "^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"
                        },
                        "textColor": {
                          "description": "textColor as #rrggbb or #rrggbbaa (alpha = opacity)",
                          "type": "string",
                          "pattern": "^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"
                        },
                        "textBackgroundColor": {
                          "description": "textBackgroundColor as #rrggbb or #rrggbbaa (alpha = opacity)",
                          "type": "string",
                          "pattern": "^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"
                        },
                        "shadowColor": {
                          "description": "shadowColor as #rrggbb or #rrggbbaa (alpha = opacity)",
                          "type": "string",
                          "pattern": "^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"
                        },
                        "interpolation": {
                          "description": "Easing for THIS entry, applied to every property in it (default easeInOut). NOT `easing`.",
                          "type": "string",
                          "enum": [
                            "linear",
                            "easeInOut",
                            "easeIn",
                            "easeOut",
                            "sigmoid",
                            "smootherstep",
                            "smoothstep",
                            "hold",
                            "easeInBack",
                            "easeOutBack",
                            "easeInOutBack",
                            "custom"
                          ]
                        },
                        "interpolation_overrides": {
                          "description": "Per-property easing override, e.g. {\"opacity\":\"linear\"} while the rest use `interpolation`. Keys must be keyframable properties present in this entry.",
                          "type": "object",
                          "propertyNames": {
                            "type": "string",
                            "enum": [
                              "positionX",
                              "positionY",
                              "width",
                              "height",
                              "rotation",
                              "scaleX",
                              "scaleY",
                              "rotateX",
                              "rotateY",
                              "cropX",
                              "cropY",
                              "cropWidth",
                              "cropHeight",
                              "opacity",
                              "blur",
                              "backgroundBlur",
                              "strokeWidth",
                              "strokeDash",
                              "strokeGap",
                              "strokeTrimStart",
                              "strokeTrimEnd",
                              "strokeTrimOffset",
                              "textStrokeWidth",
                              "textStrokeDash",
                              "textStrokeGap",
                              "imageBorderWidth",
                              "shadowOffsetX",
                              "shadowOffsetY",
                              "shadowBlur",
                              "shadowSpread",
                              "borderRadiusTopLeft",
                              "borderRadiusTopRight",
                              "borderRadiusBottomRight",
                              "borderRadiusBottomLeft",
                              "imageCornerRadiusTopLeft",
                              "imageCornerRadiusTopRight",
                              "imageCornerRadiusBottomRight",
                              "imageCornerRadiusBottomLeft",
                              "videoCornerRadiusTopLeft",
                              "videoCornerRadiusTopRight",
                              "videoCornerRadiusBottomRight",
                              "videoCornerRadiusBottomLeft",
                              "fontSize",
                              "lineHeight",
                              "letterSpacing",
                              "paddingTop",
                              "paddingRight",
                              "paddingBottom",
                              "paddingLeft",
                              "textBgRadiusTopLeft",
                              "textBgRadiusTopRight",
                              "textBgRadiusBottomRight",
                              "textBgRadiusBottomLeft",
                              "fillColor",
                              "strokeColor",
                              "textStrokeColor",
                              "imageBorderColor",
                              "textColor",
                              "textBackgroundColor",
                              "shadowColor"
                            ]
                          },
                          "additionalProperties": {
                            "type": "string",
                            "enum": [
                              "linear",
                              "easeInOut",
                              "easeIn",
                              "easeOut",
                              "sigmoid",
                              "smootherstep",
                              "smoothstep",
                              "hold",
                              "easeInBack",
                              "easeOutBack",
                              "easeInOutBack",
                              "custom"
                            ]
                          }
                        },
                        "bezier": {
                          "description": "Cubic-bezier [x1,y1,x2,y2]; used when interpolation is 'custom'.",
                          "minItems": 4,
                          "maxItems": 4,
                          "type": "array",
                          "items": {
                            "type": "number"
                          }
                        },
                        "bezier_overrides": {
                          "description": "Per-property cubic-bezier override, e.g. {\"opacity\":[.4,0,.6,1]}, when two `custom`-eased properties in this entry need DIFFERENT curves. Keys must be properties present in this entry.",
                          "type": "object",
                          "propertyNames": {
                            "type": "string",
                            "enum": [
                              "positionX",
                              "positionY",
                              "width",
                              "height",
                              "rotation",
                              "scaleX",
                              "scaleY",
                              "rotateX",
                              "rotateY",
                              "cropX",
                              "cropY",
                              "cropWidth",
                              "cropHeight",
                              "opacity",
                              "blur",
                              "backgroundBlur",
                              "strokeWidth",
                              "strokeDash",
                              "strokeGap",
                              "strokeTrimStart",
                              "strokeTrimEnd",
                              "strokeTrimOffset",
                              "textStrokeWidth",
                              "textStrokeDash",
                              "textStrokeGap",
                              "imageBorderWidth",
                              "shadowOffsetX",
                              "shadowOffsetY",
                              "shadowBlur",
                              "shadowSpread",
                              "borderRadiusTopLeft",
                              "borderRadiusTopRight",
                              "borderRadiusBottomRight",
                              "borderRadiusBottomLeft",
                              "imageCornerRadiusTopLeft",
                              "imageCornerRadiusTopRight",
                              "imageCornerRadiusBottomRight",
                              "imageCornerRadiusBottomLeft",
                              "videoCornerRadiusTopLeft",
                              "videoCornerRadiusTopRight",
                              "videoCornerRadiusBottomRight",
                              "videoCornerRadiusBottomLeft",
                              "fontSize",
                              "lineHeight",
                              "letterSpacing",
                              "paddingTop",
                              "paddingRight",
                              "paddingBottom",
                              "paddingLeft",
                              "textBgRadiusTopLeft",
                              "textBgRadiusTopRight",
                              "textBgRadiusBottomRight",
                              "textBgRadiusBottomLeft",
                              "fillColor",
                              "strokeColor",
                              "textStrokeColor",
                              "imageBorderColor",
                              "textColor",
                              "textBackgroundColor",
                              "shadowColor"
                            ]
                          },
                          "additionalProperties": {
                            "minItems": 4,
                            "maxItems": 4,
                            "type": "array",
                            "items": {
                              "type": "number"
                            }
                          }
                        }
                      },
                      "required": [
                        "timestamp"
                      ],
                      "additionalProperties": false
                    }
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "keyframes_repeat": {
                "description": "Repeat `keyframes` as a cycle \u2014 { count, mode?, every? }. Requires `keyframes` in the same call.",
                "type": "object",
                "properties": {
                  "count": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 9007199254740991,
                    "description": "TOTAL number of cycles, INCLUDING the one you wrote. 1 is a no-op. Expansion stops at the clip end rather than piling the overflow onto the last frame, and the generated total is capped at 600 keyframes (it errors rather than silently truncating)."
                  },
                  "mode": {
                    "description": "'loop' (default) replays the cycle forwards every time \u2014 seamless only if your first and last values match. 'pingpong' alternates forwards/backwards, so the value always returns to where it started without a jump; use it for a breathe/pulse written as a one-way ramp.",
                    "type": "string",
                    "enum": [
                      "loop",
                      "pingpong"
                    ]
                  },
                  "every": {
                    "description": "Seconds between cycle STARTS. Defaults to the cycle's own span (back-to-back). A LARGER value holds the last value in the gap \u2014 that is how you get a pulse every 2s instead of a constant wobble. Shorter than the cycle is rejected, because the cycles would overlap and collide on the same frames.",
                    "type": "number",
                    "exclusiveMinimum": 0
                  }
                },
                "required": [
                  "count"
                ],
                "additionalProperties": false
              },
              "clip_id": {
                "description": "Clip ID \u2014 accepted but unused; the clip is located by clip_index for all element types.",
                "type": "string"
              },
              "continue_from": {
                "description": "Carry an element across the cut instead of adding a new one: an element_id or group id from an earlier clip. clip_index is the clip it continues into (default: the next clip after the source). A native element, or every element of a group, is copied at t=0 holding its end state (keyframes evaluated at its end) with no entry animation. An HTML animation element is placed again picking up its page where it stopped (same revision, box, parameter values and timeline; media_offset in the reply); one inside a carried group is carried on its own. Besides clip_index the item takes only start_time / end_time (clip seconds; default the cut and the clip's end), insert_at, and type_data: {animation_setting: {exit}} so a carried text, image or video leaves on time. Stacking: the carried elements keep their source order and land just above the target clip's backdrop (a full-canvas scene or background), under its other elements, unless insert_at says; an overlay that must stay on top (a cursor, a caption pill): add it last or pass insert_at: 'front'. Into the next clip, from a source that lasts to its clip's end, the source's exit and end fade are taken off (the cut is the continuity) and listed as source_updated. Returns the new element_ids (a group as a new group, renamed in group_name if the target clip has its name); anything not brought across is listed in not_carried.",
                "type": "string",
                "minLength": 1
              },
              "type_data": {
                "description": "Type-specific fields. Use the get_element_schema tool first to discover the shape for a given element_type. Omit when using component_id.",
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {}
              },
              "cues": {
                "description": "Animation elements only: the same list as type_data.cues, accepted here too (send it in one place). E.g. update_elements({updates:[{element_id, cues:[{op:'set', key, wordIndex, offset}]}]}).",
                "type": "array",
                "items": {}
              },
              "param_tracks": {
                "description": "Animation elements only: the same list as type_data.param_tracks, accepted here too (send it in one place).",
                "type": "array",
                "items": {}
              },
              "paramTracks": {
                "description": "Same as param_tracks.",
                "type": "array",
                "items": {}
              },
              "actions": {
                "description": "Animation elements only: the same list as type_data.actions, accepted here too (send it in one place).",
                "type": "array",
                "items": {}
              }
            },
            "additionalProperties": false
          },
          "description": "Elements to add \u2014 at least one entry."
        },
        "defaults": {
          "description": "Values shared by every item, sent once rather than repeated. Items win per key; type_data merges one level. E.g. defaults:{clip_index:1, element_type:'text', type_data:{font_setting:{font:'Inter',weight:'600'}, alignment:'center'}} then items carry only what differs.",
          "type": "object",
          "properties": {
            "cues": {
              "description": "Animation elements only: the same list as type_data.cues, accepted here too (send it in one place). E.g. update_elements({updates:[{element_id, cues:[{op:'set', key, wordIndex, offset}]}]}).",
              "type": "array",
              "items": {}
            },
            "param_tracks": {
              "description": "Animation elements only: the same list as type_data.param_tracks, accepted here too (send it in one place).",
              "type": "array",
              "items": {}
            },
            "paramTracks": {
              "description": "Same as param_tracks.",
              "type": "array",
              "items": {}
            },
            "actions": {
              "description": "Animation elements only: the same list as type_data.actions, accepted here too (send it in one place).",
              "type": "array",
              "items": {}
            },
            "clip_index": {
              "type": "integer",
              "minimum": 0,
              "maximum": 9007199254740991
            },
            "clip_id": {
              "type": "string"
            },
            "element_type": {
              "type": "string",
              "enum": [
                "text",
                "callout",
                "rectangle",
                "arrow",
                "blur",
                "spotlight",
                "zoom",
                "image",
                "video",
                "animation",
                "question",
                "results",
                "form",
                "avatar"
              ]
            },
            "x": {
              "type": "number"
            },
            "y": {
              "type": "number"
            },
            "y_anchor": {
              "type": "string",
              "enum": [
                "top",
                "center",
                "bottom"
              ]
            },
            "width": {
              "type": "number",
              "minimum": 0
            },
            "height": {
              "type": "number",
              "minimum": 0
            },
            "start_time": {
              "type": "number",
              "minimum": 0
            },
            "end_time": {
              "type": "number",
              "minimum": 0
            },
            "name": {
              "type": "string"
            },
            "rotation": {
              "type": "number"
            },
            "group": {
              "type": "string",
              "minLength": 1
            },
            "insert_at": {
              "anyOf": [
                {
                  "type": "string",
                  "enum": [
                    "front",
                    "back"
                  ]
                },
                {
                  "type": "object",
                  "properties": {
                    "before": {
                      "type": "string",
                      "minLength": 1
                    }
                  },
                  "required": [
                    "before"
                  ],
                  "additionalProperties": false
                },
                {
                  "type": "object",
                  "properties": {
                    "after": {
                      "type": "string",
                      "minLength": 1
                    }
                  },
                  "required": [
                    "after"
                  ],
                  "additionalProperties": false
                },
                {
                  "type": "string",
                  "pattern": "^(before|after):.+$"
                }
              ]
            },
            "keyframes": {
              "description": "Same entries as an item's keyframes.",
              "anyOf": [
                {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "propertyNames": {
                      "type": "string"
                    },
                    "additionalProperties": {}
                  }
                },
                {
                  "type": "null"
                }
              ]
            },
            "keyframes_repeat": {
              "description": "Same as an item's keyframes_repeat.",
              "type": "object",
              "propertyNames": {
                "type": "string"
              },
              "additionalProperties": {}
            },
            "type_data": {
              "type": "object",
              "propertyNames": {
                "type": "string"
              },
              "additionalProperties": {}
            }
          },
          "additionalProperties": false
        },
        "returning": {
          "description": "What comes back, the way get_clip's `select` shapes a read. Omitted: ids, the measured layout and the clip audit in compact form. 'ids': element_id per result. 'layout': the measured text fit (font_size_px, text_width_px, natural_width_px, line_count, fits_width/fits_height/fits \u2014 widen a box to natural_width_px, not text_width_px; an `adjusted` entry when the measured line box replaced your height) plus `placement` {y_anchor, top, center, bottom}: compare placement.center with a container's centre line to verify centring. 'qa.codes': the clip audit with severity/code/element_ids per finding; 'qa': with the message and fix on each. Errors always come back: in full under ['ids'], as {clip_index, errors: [{code, element_ids}]} under []; bleed under a keyframed group is a warning marked camera_move. A failed item and font_warnings always come back. [] is the leanest write: {total, succeeded, failed} plus errors.",
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "ids",
              "layout",
              "qa",
              "qa.codes"
            ]
          }
        }
      },
      "required": [
        "project_id",
        "elements"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "update_elements": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "string",
          "description": "Project ID"
        },
        "updates": {
          "minItems": 1,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "clip_index": {
                "description": "Zero-based clip index. OPTIONAL \u2014 `element_id` is unique within the project, so the server finds the clip itself. Pass it only as a fast-path hint; a wrong one is corrected rather than rejected.",
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              "element_id": {
                "type": "string",
                "description": "Element ID (from get_clip)"
              },
              "group": {
                "description": "Move this element into a named group, creating it if this clip has none by that name. Pass an empty string to pull it back out to the clip root. Omit to leave grouping unchanged. Grouping never moves anything \u2014 coordinates stay absolute.",
                "type": "string"
              },
              "parent_id": {
                "description": "The parent `get_clip` reports on this element: a group id, or the clip id for an ungrouped element. Accepted so a read round-trips unedited \u2014 the element's current parent is a no-op and leaves z-order alone. A different group id on this element's clip moves it there (the group must have a name); the clip id or null moves it to the root. Use `group` when you are choosing a group \u2014 the name is the handle. `group` wins if both are sent.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "insert_at": {
                "description": "Move this element in z-order, without rebuilding it. `front`/`back` are the ends of the group it is already in (the clip itself for an ungrouped element), so they never change what it belongs to \u2014 with one exception: for an element that is ALONE in its group they move the GROUP within its parent scope, because positioning a sole member inside its own group would be a no-op. `{before: element_id}` / `{after: element_id}` sit it next to that element or group and put it in THAT node's group \u2014 which is how you move something into or out of a group and position it in one call. Applied after `group` when both are passed.",
                "anyOf": [
                  {
                    "type": "string",
                    "enum": [
                      "front",
                      "back"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "before": {
                        "type": "string",
                        "minLength": 1
                      }
                    },
                    "required": [
                      "before"
                    ],
                    "additionalProperties": false
                  },
                  {
                    "type": "object",
                    "properties": {
                      "after": {
                        "type": "string",
                        "minLength": 1
                      }
                    },
                    "required": [
                      "after"
                    ],
                    "additionalProperties": false
                  },
                  {
                    "type": "string",
                    "pattern": "^(before|after):.+$"
                  }
                ]
              },
              "x": {
                "description": "New X (canvas pixels). Same origin as add_elements: `x` is the left edge, except TEXT where alignment picks it: left\u2192left, center\u2192centre, right\u2192right. Changing alignment in the same call re-anchors x to the new edge.",
                "type": "number"
              },
              "y": {
                "description": "New Y (canvas pixels) \u2014 for TEXT `y` is the vertical CENTRE when centre-aligned and the TOP otherwise; every other type uses the top. See y_anchor.",
                "type": "number"
              },
              "y_anchor": {
                "description": "TEXT only. ALWAYS send `y_anchor` ('top'|'center'|'bottom') when you set `y` on text, so the position states what you meant instead of inheriting the alignment origin. Omit `y` and it anchors to the element's CURRENT box, holding a centred label centred across a re-measure. Not stored \u2014 state it on every call. (Also accepted inside type_data; this one wins.)",
                "type": "string",
                "enum": [
                  "top",
                  "center",
                  "bottom"
                ]
              },
              "width": {
                "description": "New width, pixels (0 allowed \u2014 e.g. a bar that draws in from 0). For text this is the wrap width and re-wraps the text.",
                "type": "number",
                "minimum": 0
              },
              "height": {
                "description": "New height, pixels (0 allowed). For text, the space you are giving it \u2014 the box resizes to the measured text. See layout.fits_height in the response.",
                "type": "number",
                "minimum": 0
              },
              "start_time": {
                "description": "New time the element appears, in seconds from the start of the clip.",
                "type": "number",
                "minimum": 0
              },
              "end_time": {
                "description": "New time the element disappears, in seconds from the start of the clip.",
                "type": "number",
                "minimum": 0
              },
              "name": {
                "description": "New label for the element.",
                "type": "string"
              },
              "rotation": {
                "description": "Rotation in degrees",
                "type": "number"
              },
              "keyframes": {
                "description": "Set keyframes for this element. Only the property tracks present in your entries are replaced \u2014 other tracks (color/rotation/typography/\u2026) are preserved. Pass null OR an empty array to clear ALL keyframes. Same per-entry shape as add_elements; see get_element_schema for the keyframable properties of this element_type.",
                "anyOf": [
                  {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "timestamp": {
                          "type": "number",
                          "description": "Clip-relative time in seconds, 0 or later (0 = the clip's first frame)"
                        },
                        "positionX": {
                          "description": "X position in canvas pixels (NOT `x`). Same origin as the element's top-level x: `x` is the left edge, except TEXT where alignment picks it: left\u2192left, center\u2192centre, right\u2192right.",
                          "type": "number"
                        },
                        "positionY": {
                          "description": "Y position in canvas pixels (NOT `y`). On TEXT whose item also sends y with y_anchor, positionY means the same point y does (e.g. y_anchor:'center' \u2192 the line the text is centred on), so a tween from y+40 to y slides it onto that line: the server shifts the track by the offset it applied to y and says so in keyframes_shifted. Otherwise the element's stored origin: for TEXT `y` is the vertical CENTRE when centre-aligned and the TOP otherwise; every other type uses the top.",
                          "type": "number"
                        },
                        "width": {
                          "description": "Width in canvas pixels (0 allowed \u2014 e.g. a bar that draws in from 0)",
                          "type": "number",
                          "minimum": 0
                        },
                        "height": {
                          "description": "Height in canvas pixels (0 allowed)",
                          "type": "number",
                          "minimum": 0
                        },
                        "rotation": {
                          "description": "Rotation in degrees",
                          "type": "number"
                        },
                        "scaleX": {
                          "description": "Horizontal scale multiplier (1 = 100%)",
                          "type": "number"
                        },
                        "scaleY": {
                          "description": "Vertical scale multiplier (1 = 100%)",
                          "type": "number"
                        },
                        "rotateX": {
                          "description": "rotateX (native unit)",
                          "type": "number"
                        },
                        "rotateY": {
                          "description": "rotateY (native unit)",
                          "type": "number"
                        },
                        "cropX": {
                          "description": "cropX (native unit)",
                          "type": "number"
                        },
                        "cropY": {
                          "description": "cropY (native unit)",
                          "type": "number"
                        },
                        "cropWidth": {
                          "description": "cropWidth (native unit)",
                          "type": "number"
                        },
                        "cropHeight": {
                          "description": "cropHeight (native unit)",
                          "type": "number"
                        },
                        "opacity": {
                          "description": "Opacity 0-100",
                          "type": "number"
                        },
                        "blur": {
                          "description": "Layer blur",
                          "type": "number"
                        },
                        "backgroundBlur": {
                          "description": "Backdrop blur behind the element",
                          "type": "number"
                        },
                        "strokeWidth": {
                          "description": "strokeWidth in canvas pixels",
                          "type": "number"
                        },
                        "strokeDash": {
                          "description": "strokeDash in canvas pixels",
                          "type": "number"
                        },
                        "strokeGap": {
                          "description": "strokeGap in canvas pixels",
                          "type": "number"
                        },
                        "strokeTrimStart": {
                          "description": "strokeTrimStart (native unit)",
                          "type": "number"
                        },
                        "strokeTrimEnd": {
                          "description": "strokeTrimEnd (native unit)",
                          "type": "number"
                        },
                        "strokeTrimOffset": {
                          "description": "strokeTrimOffset (native unit)",
                          "type": "number"
                        },
                        "textStrokeWidth": {
                          "description": "textStrokeWidth in canvas pixels",
                          "type": "number"
                        },
                        "textStrokeDash": {
                          "description": "textStrokeDash in canvas pixels",
                          "type": "number"
                        },
                        "textStrokeGap": {
                          "description": "textStrokeGap in canvas pixels",
                          "type": "number"
                        },
                        "imageBorderWidth": {
                          "description": "imageBorderWidth in canvas pixels",
                          "type": "number"
                        },
                        "shadowOffsetX": {
                          "description": "shadowOffsetX in canvas pixels",
                          "type": "number"
                        },
                        "shadowOffsetY": {
                          "description": "shadowOffsetY in canvas pixels",
                          "type": "number"
                        },
                        "shadowBlur": {
                          "description": "shadowBlur in canvas pixels",
                          "type": "number"
                        },
                        "shadowSpread": {
                          "description": "shadowSpread in canvas pixels",
                          "type": "number"
                        },
                        "borderRadiusTopLeft": {
                          "description": "borderRadiusTopLeft in canvas pixels",
                          "type": "number"
                        },
                        "borderRadiusTopRight": {
                          "description": "borderRadiusTopRight in canvas pixels",
                          "type": "number"
                        },
                        "borderRadiusBottomRight": {
                          "description": "borderRadiusBottomRight in canvas pixels",
                          "type": "number"
                        },
                        "borderRadiusBottomLeft": {
                          "description": "borderRadiusBottomLeft in canvas pixels",
                          "type": "number"
                        },
                        "imageCornerRadiusTopLeft": {
                          "description": "imageCornerRadiusTopLeft in canvas pixels",
                          "type": "number"
                        },
                        "imageCornerRadiusTopRight": {
                          "description": "imageCornerRadiusTopRight in canvas pixels",
                          "type": "number"
                        },
                        "imageCornerRadiusBottomRight": {
                          "description": "imageCornerRadiusBottomRight in canvas pixels",
                          "type": "number"
                        },
                        "imageCornerRadiusBottomLeft": {
                          "description": "imageCornerRadiusBottomLeft in canvas pixels",
                          "type": "number"
                        },
                        "videoCornerRadiusTopLeft": {
                          "description": "videoCornerRadiusTopLeft in canvas pixels",
                          "type": "number"
                        },
                        "videoCornerRadiusTopRight": {
                          "description": "videoCornerRadiusTopRight in canvas pixels",
                          "type": "number"
                        },
                        "videoCornerRadiusBottomRight": {
                          "description": "videoCornerRadiusBottomRight in canvas pixels",
                          "type": "number"
                        },
                        "videoCornerRadiusBottomLeft": {
                          "description": "videoCornerRadiusBottomLeft in canvas pixels",
                          "type": "number"
                        },
                        "fontSize": {
                          "description": "Font size in CANVAS PIXELS (same unit as the top-level fontSize \u2014 NOT a fraction).",
                          "type": "number"
                        },
                        "lineHeight": {
                          "description": "lineHeight (native unit)",
                          "type": "number"
                        },
                        "letterSpacing": {
                          "description": "Letter spacing in canvas pixels.",
                          "type": "number"
                        },
                        "paddingTop": {
                          "description": "paddingTop in canvas pixels",
                          "type": "number"
                        },
                        "paddingRight": {
                          "description": "paddingRight in canvas pixels",
                          "type": "number"
                        },
                        "paddingBottom": {
                          "description": "paddingBottom in canvas pixels",
                          "type": "number"
                        },
                        "paddingLeft": {
                          "description": "paddingLeft in canvas pixels",
                          "type": "number"
                        },
                        "textBgRadiusTopLeft": {
                          "description": "textBgRadiusTopLeft in canvas pixels",
                          "type": "number"
                        },
                        "textBgRadiusTopRight": {
                          "description": "textBgRadiusTopRight in canvas pixels",
                          "type": "number"
                        },
                        "textBgRadiusBottomRight": {
                          "description": "textBgRadiusBottomRight in canvas pixels",
                          "type": "number"
                        },
                        "textBgRadiusBottomLeft": {
                          "description": "textBgRadiusBottomLeft in canvas pixels",
                          "type": "number"
                        },
                        "fillColor": {
                          "description": "fillColor as #rrggbb or #rrggbbaa (alpha = opacity)",
                          "type": "string",
                          "pattern": "^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"
                        },
                        "strokeColor": {
                          "description": "strokeColor as #rrggbb or #rrggbbaa (alpha = opacity)",
                          "type": "string",
                          "pattern": "^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"
                        },
                        "textStrokeColor": {
                          "description": "textStrokeColor as #rrggbb or #rrggbbaa (alpha = opacity)",
                          "type": "string",
                          "pattern": "^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"
                        },
                        "imageBorderColor": {
                          "description": "imageBorderColor as #rrggbb or #rrggbbaa (alpha = opacity)",
                          "type": "string",
                          "pattern": "^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"
                        },
                        "textColor": {
                          "description": "textColor as #rrggbb or #rrggbbaa (alpha = opacity)",
                          "type": "string",
                          "pattern": "^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"
                        },
                        "textBackgroundColor": {
                          "description": "textBackgroundColor as #rrggbb or #rrggbbaa (alpha = opacity)",
                          "type": "string",
                          "pattern": "^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"
                        },
                        "shadowColor": {
                          "description": "shadowColor as #rrggbb or #rrggbbaa (alpha = opacity)",
                          "type": "string",
                          "pattern": "^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"
                        },
                        "interpolation": {
                          "description": "Easing for THIS entry, applied to every property in it (default easeInOut). NOT `easing`.",
                          "type": "string",
                          "enum": [
                            "linear",
                            "easeInOut",
                            "easeIn",
                            "easeOut",
                            "sigmoid",
                            "smootherstep",
                            "smoothstep",
                            "hold",
                            "easeInBack",
                            "easeOutBack",
                            "easeInOutBack",
                            "custom"
                          ]
                        },
                        "interpolation_overrides": {
                          "description": "Per-property easing override, e.g. {\"opacity\":\"linear\"} while the rest use `interpolation`. Keys must be keyframable properties present in this entry.",
                          "type": "object",
                          "propertyNames": {
                            "type": "string",
                            "enum": [
                              "positionX",
                              "positionY",
                              "width",
                              "height",
                              "rotation",
                              "scaleX",
                              "scaleY",
                              "rotateX",
                              "rotateY",
                              "cropX",
                              "cropY",
                              "cropWidth",
                              "cropHeight",
                              "opacity",
                              "blur",
                              "backgroundBlur",
                              "strokeWidth",
                              "strokeDash",
                              "strokeGap",
                              "strokeTrimStart",
                              "strokeTrimEnd",
                              "strokeTrimOffset",
                              "textStrokeWidth",
                              "textStrokeDash",
                              "textStrokeGap",
                              "imageBorderWidth",
                              "shadowOffsetX",
                              "shadowOffsetY",
                              "shadowBlur",
                              "shadowSpread",
                              "borderRadiusTopLeft",
                              "borderRadiusTopRight",
                              "borderRadiusBottomRight",
                              "borderRadiusBottomLeft",
                              "imageCornerRadiusTopLeft",
                              "imageCornerRadiusTopRight",
                              "imageCornerRadiusBottomRight",
                              "imageCornerRadiusBottomLeft",
                              "videoCornerRadiusTopLeft",
                              "videoCornerRadiusTopRight",
                              "videoCornerRadiusBottomRight",
                              "videoCornerRadiusBottomLeft",
                              "fontSize",
                              "lineHeight",
                              "letterSpacing",
                              "paddingTop",
                              "paddingRight",
                              "paddingBottom",
                              "paddingLeft",
                              "textBgRadiusTopLeft",
                              "textBgRadiusTopRight",
                              "textBgRadiusBottomRight",
                              "textBgRadiusBottomLeft",
                              "fillColor",
                              "strokeColor",
                              "textStrokeColor",
                              "imageBorderColor",
                              "textColor",
                              "textBackgroundColor",
                              "shadowColor"
                            ]
                          },
                          "additionalProperties": {
                            "type": "string",
                            "enum": [
                              "linear",
                              "easeInOut",
                              "easeIn",
                              "easeOut",
                              "sigmoid",
                              "smootherstep",
                              "smoothstep",
                              "hold",
                              "easeInBack",
                              "easeOutBack",
                              "easeInOutBack",
                              "custom"
                            ]
                          }
                        },
                        "bezier": {
                          "description": "Cubic-bezier [x1,y1,x2,y2]; used when interpolation is 'custom'.",
                          "minItems": 4,
                          "maxItems": 4,
                          "type": "array",
                          "items": {
                            "type": "number"
                          }
                        },
                        "bezier_overrides": {
                          "description": "Per-property cubic-bezier override, e.g. {\"opacity\":[.4,0,.6,1]}, when two `custom`-eased properties in this entry need DIFFERENT curves. Keys must be properties present in this entry.",
                          "type": "object",
                          "propertyNames": {
                            "type": "string",
                            "enum": [
                              "positionX",
                              "positionY",
                              "width",
                              "height",
                              "rotation",
                              "scaleX",
                              "scaleY",
                              "rotateX",
                              "rotateY",
                              "cropX",
                              "cropY",
                              "cropWidth",
                              "cropHeight",
                              "opacity",
                              "blur",
                              "backgroundBlur",
                              "strokeWidth",
                              "strokeDash",
                              "strokeGap",
                              "strokeTrimStart",
                              "strokeTrimEnd",
                              "strokeTrimOffset",
                              "textStrokeWidth",
                              "textStrokeDash",
                              "textStrokeGap",
                              "imageBorderWidth",
                              "shadowOffsetX",
                              "shadowOffsetY",
                              "shadowBlur",
                              "shadowSpread",
                              "borderRadiusTopLeft",
                              "borderRadiusTopRight",
                              "borderRadiusBottomRight",
                              "borderRadiusBottomLeft",
                              "imageCornerRadiusTopLeft",
                              "imageCornerRadiusTopRight",
                              "imageCornerRadiusBottomRight",
                              "imageCornerRadiusBottomLeft",
                              "videoCornerRadiusTopLeft",
                              "videoCornerRadiusTopRight",
                              "videoCornerRadiusBottomRight",
                              "videoCornerRadiusBottomLeft",
                              "fontSize",
                              "lineHeight",
                              "letterSpacing",
                              "paddingTop",
                              "paddingRight",
                              "paddingBottom",
                              "paddingLeft",
                              "textBgRadiusTopLeft",
                              "textBgRadiusTopRight",
                              "textBgRadiusBottomRight",
                              "textBgRadiusBottomLeft",
                              "fillColor",
                              "strokeColor",
                              "textStrokeColor",
                              "imageBorderColor",
                              "textColor",
                              "textBackgroundColor",
                              "shadowColor"
                            ]
                          },
                          "additionalProperties": {
                            "minItems": 4,
                            "maxItems": 4,
                            "type": "array",
                            "items": {
                              "type": "number"
                            }
                          }
                        }
                      },
                      "required": [
                        "timestamp"
                      ],
                      "additionalProperties": false
                    }
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "keyframes_repeat": {
                "description": "Repeat `keyframes` as a cycle \u2014 { count, mode?, every? }. Requires `keyframes` in the same call.",
                "type": "object",
                "properties": {
                  "count": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 9007199254740991,
                    "description": "TOTAL number of cycles, INCLUDING the one you wrote. 1 is a no-op. Expansion stops at the clip end rather than piling the overflow onto the last frame, and the generated total is capped at 600 keyframes (it errors rather than silently truncating)."
                  },
                  "mode": {
                    "description": "'loop' (default) replays the cycle forwards every time \u2014 seamless only if your first and last values match. 'pingpong' alternates forwards/backwards, so the value always returns to where it started without a jump; use it for a breathe/pulse written as a one-way ramp.",
                    "type": "string",
                    "enum": [
                      "loop",
                      "pingpong"
                    ]
                  },
                  "every": {
                    "description": "Seconds between cycle STARTS. Defaults to the cycle's own span (back-to-back). A LARGER value holds the last value in the gap \u2014 that is how you get a pulse every 2s instead of a constant wobble. Shorter than the cycle is rejected, because the cycles would overlap and collide on the same frames.",
                    "type": "number",
                    "exclusiveMinimum": 0
                  }
                },
                "required": [
                  "count"
                ],
                "additionalProperties": false
              },
              "element_type": {
                "description": "Required only when providing type_data. Use get_element_schema in 'update' mode to see fields.",
                "type": "string",
                "enum": [
                  "text",
                  "callout",
                  "rectangle",
                  "arrow",
                  "blur",
                  "spotlight",
                  "zoom",
                  "image",
                  "video",
                  "animation",
                  "question",
                  "results",
                  "form",
                  "avatar"
                ]
              },
              "type_data": {
                "description": "Type-specific updates. When provided, element_type is required. Get the schema via get_element_schema.",
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {}
              },
              "cues": {
                "description": "Animation elements only: the same list as type_data.cues, accepted here too (send it in one place). E.g. update_elements({updates:[{element_id, cues:[{op:'set', key, wordIndex, offset}]}]}).",
                "type": "array",
                "items": {}
              },
              "param_tracks": {
                "description": "Animation elements only: the same list as type_data.param_tracks, accepted here too (send it in one place).",
                "type": "array",
                "items": {}
              },
              "paramTracks": {
                "description": "Same as param_tracks.",
                "type": "array",
                "items": {}
              },
              "actions": {
                "description": "Animation elements only: the same list as type_data.actions, accepted here too (send it in one place).",
                "type": "array",
                "items": {}
              },
              "save_as_component": {
                "description": "Animation elements only. Set true to save the current state as a reusable animation component (auto-named from the animation title), or pass a string to name it explicitly. Save runs AFTER any position/type_data updates land. Returns the component_id alongside the per-update result. An HTML Mograph (codedata.version 2) is saved with its page and current parameter values, so inserting the component later plays it as saved.",
                "type": [
                  "boolean",
                  "string"
                ]
              },
              "fit_to_lines": {
                "description": "Text elements only. Target line count for the element, or {lines, allow_width_increase?, max_width_fraction?, min_font_scale?}. After this entry's regular updates land, the server widens (then font-shrinks if widen can't fit) the element so the rendered text wraps to at most N lines without overlapping its neighbours. Uses the same compute path as director's fit_text_layout.",
                "anyOf": [
                  {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 9007199254740991
                  },
                  {
                    "type": "object",
                    "properties": {
                      "lines": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 9007199254740991
                      },
                      "allow_width_increase": {
                        "description": "Default true",
                        "type": "boolean"
                      },
                      "max_width_fraction": {
                        "description": "Widest the box may grow, as a fraction of the canvas (0.95)",
                        "type": "number",
                        "exclusiveMinimum": 0,
                        "maximum": 1
                      },
                      "min_font_scale": {
                        "description": "Smallest font, as a fraction of the current size (0.8)",
                        "type": "number",
                        "exclusiveMinimum": 0,
                        "maximum": 1
                      }
                    },
                    "required": [
                      "lines"
                    ],
                    "additionalProperties": false
                  }
                ]
              }
            },
            "required": [
              "element_id"
            ],
            "additionalProperties": false
          },
          "description": "Element updates \u2014 at least one entry."
        },
        "defaults": {
          "description": "Values shared by every item, sent once rather than repeated. Items win per key; type_data merges one level. E.g. defaults:{clip_index:1, element_type:'text', type_data:{font_setting:{font:'Inter',weight:'600'}, alignment:'center'}} then items carry only what differs.",
          "type": "object",
          "properties": {
            "cues": {
              "description": "Animation elements only: the same list as type_data.cues, accepted here too (send it in one place). E.g. update_elements({updates:[{element_id, cues:[{op:'set', key, wordIndex, offset}]}]}).",
              "type": "array",
              "items": {}
            },
            "param_tracks": {
              "description": "Animation elements only: the same list as type_data.param_tracks, accepted here too (send it in one place).",
              "type": "array",
              "items": {}
            },
            "paramTracks": {
              "description": "Same as param_tracks.",
              "type": "array",
              "items": {}
            },
            "actions": {
              "description": "Animation elements only: the same list as type_data.actions, accepted here too (send it in one place).",
              "type": "array",
              "items": {}
            },
            "clip_index": {
              "type": "integer",
              "minimum": 0,
              "maximum": 9007199254740991
            },
            "clip_id": {
              "type": "string"
            },
            "element_type": {
              "type": "string",
              "enum": [
                "text",
                "callout",
                "rectangle",
                "arrow",
                "blur",
                "spotlight",
                "zoom",
                "image",
                "video",
                "animation",
                "question",
                "results",
                "form",
                "avatar"
              ]
            },
            "x": {
              "type": "number"
            },
            "y": {
              "type": "number"
            },
            "y_anchor": {
              "type": "string",
              "enum": [
                "top",
                "center",
                "bottom"
              ]
            },
            "width": {
              "type": "number",
              "minimum": 0
            },
            "height": {
              "type": "number",
              "minimum": 0
            },
            "start_time": {
              "type": "number",
              "minimum": 0
            },
            "end_time": {
              "type": "number",
              "minimum": 0
            },
            "name": {
              "type": "string"
            },
            "rotation": {
              "type": "number"
            },
            "group": {
              "type": "string",
              "minLength": 1
            },
            "insert_at": {
              "anyOf": [
                {
                  "type": "string",
                  "enum": [
                    "front",
                    "back"
                  ]
                },
                {
                  "type": "object",
                  "properties": {
                    "before": {
                      "type": "string",
                      "minLength": 1
                    }
                  },
                  "required": [
                    "before"
                  ],
                  "additionalProperties": false
                },
                {
                  "type": "object",
                  "properties": {
                    "after": {
                      "type": "string",
                      "minLength": 1
                    }
                  },
                  "required": [
                    "after"
                  ],
                  "additionalProperties": false
                },
                {
                  "type": "string",
                  "pattern": "^(before|after):.+$"
                }
              ]
            },
            "keyframes": {
              "description": "Same entries as an item's keyframes.",
              "anyOf": [
                {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "propertyNames": {
                      "type": "string"
                    },
                    "additionalProperties": {}
                  }
                },
                {
                  "type": "null"
                }
              ]
            },
            "keyframes_repeat": {
              "description": "Same as an item's keyframes_repeat.",
              "type": "object",
              "propertyNames": {
                "type": "string"
              },
              "additionalProperties": {}
            },
            "type_data": {
              "type": "object",
              "propertyNames": {
                "type": "string"
              },
              "additionalProperties": {}
            }
          },
          "additionalProperties": false
        },
        "returning": {
          "description": "What comes back, the way get_clip's `select` shapes a read. Omitted: ids, the measured layout and the clip audit in compact form. 'ids': element_id per result. 'layout': the measured text fit (font_size_px, text_width_px, natural_width_px, line_count, fits_width/fits_height/fits \u2014 widen a box to natural_width_px, not text_width_px; an `adjusted` entry when the measured line box replaced your height) plus `placement` {y_anchor, top, center, bottom}: compare placement.center with a container's centre line to verify centring. 'qa.codes': the clip audit with severity/code/element_ids per finding; 'qa': with the message and fix on each. Errors always come back: in full under ['ids'], as {clip_index, errors: [{code, element_ids}]} under []; bleed under a keyframed group is a warning marked camera_move. A failed item and font_warnings always come back. [] is the leanest write: {total, succeeded, failed} plus errors.",
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "ids",
              "layout",
              "qa",
              "qa.codes"
            ]
          }
        }
      },
      "required": [
        "project_id",
        "updates"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "remove_elements": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "string",
          "description": "Project ID"
        },
        "removals": {
          "minItems": 1,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "clip_index": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991,
                "description": "Zero-based clip index"
              },
              "element_id": {
                "type": "string",
                "description": "Element ID (from get_clip)"
              }
            },
            "required": [
              "clip_index",
              "element_id"
            ],
            "additionalProperties": false
          },
          "description": "Elements to remove \u2014 at least one entry."
        }
      },
      "required": [
        "project_id",
        "removals"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "reorder_elements": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "string",
          "description": "Project ID."
        },
        "clip_index": {
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991,
          "description": "Zero-based clip index."
        },
        "ordered_element_ids": {
          "minItems": 1,
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "All element IDs currently in the clip, in the new order. Position N = z-index N (later = on top)."
        }
      },
      "required": [
        "project_id",
        "clip_index",
        "ordered_element_ids"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "update_groups": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "string",
          "description": "The project ID"
        },
        "items": {
          "minItems": 1,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "clip_index": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991,
                "description": "Zero-based clip index"
              },
              "group_id": {
                "type": "string",
                "minLength": 1,
                "description": "The group's id (from get_clip, or returned by add_elements when you pass `group`)"
              },
              "name": {
                "description": "Rename the group. Names stay unique per clip, so a collision is rejected.",
                "type": "string",
                "minLength": 1
              },
              "parent_group_id": {
                "description": "Nest this group under another by id. \"\" moves it to the clip root. Omit to leave the parent alone.",
                "type": "string"
              },
              "locked": {
                "description": "Lock the group in the editor",
                "type": "boolean"
              },
              "collapsed": {
                "description": "Collapse the group in the editor's layer tree",
                "type": "boolean"
              },
              "offset_px": {
                "description": "Move the whole group by [dx, dy] canvas pixels. Children's stored positions are untouched.",
                "type": "array",
                "prefixItems": [
                  {
                    "type": "number"
                  },
                  {
                    "type": "number"
                  }
                ],
                "items": false,
                "minItems": 2,
                "maxItems": 2
              },
              "scale": {
                "description": "Scale [x, y] as a multiplier about the anchor (1 = unscaled)",
                "type": "array",
                "prefixItems": [
                  {
                    "type": "number"
                  },
                  {
                    "type": "number"
                  }
                ],
                "items": false,
                "minItems": 2,
                "maxItems": 2
              },
              "rotation": {
                "description": "Rotation in degrees about the anchor",
                "type": "number"
              },
              "rotate": {
                "description": "3D tilt [x, y] in degrees",
                "type": "array",
                "prefixItems": [
                  {
                    "type": "number"
                  },
                  {
                    "type": "number"
                  }
                ],
                "items": false,
                "minItems": 2,
                "maxItems": 2
              },
              "opacity": {
                "description": "0-100, multiplied over each child's own opacity",
                "type": "number",
                "minimum": 0,
                "maximum": 100
              },
              "blur_px": {
                "description": "Blur in pixels, applied over every child",
                "type": "number",
                "minimum": 0
              },
              "anchor_px": {
                "description": "The pivot, as a canvas-pixel point. Defaults to the centre of the group's child bounding box.",
                "type": "array",
                "prefixItems": [
                  {
                    "type": "number"
                  },
                  {
                    "type": "number"
                  }
                ],
                "items": false,
                "minItems": 2,
                "maxItems": 2
              },
              "insert_at": {
                "description": "Z-order: moves the whole group as a block; members keep their order. 'front'/'back' of its scope, or {before|after: element or group id} (taking that node's parent).",
                "anyOf": [
                  {
                    "type": "string",
                    "enum": [
                      "front",
                      "back"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "before": {
                        "type": "string",
                        "minLength": 1
                      }
                    },
                    "required": [
                      "before"
                    ],
                    "additionalProperties": false
                  },
                  {
                    "type": "object",
                    "properties": {
                      "after": {
                        "type": "string",
                        "minLength": 1
                      }
                    },
                    "required": [
                      "after"
                    ],
                    "additionalProperties": false
                  },
                  {
                    "type": "string",
                    "pattern": "^(before|after):.+$"
                  }
                ]
              },
              "keyframes": {
                "description": "Animate the group over time. [] clears every track; omit to leave tracks alone.",
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "timestamp": {
                      "type": "number",
                      "minimum": 0,
                      "description": "Clip-relative time in seconds, 0 or later (0 = the clip's first frame)"
                    },
                    "offsetX": {
                      "description": "offsetX in canvas pixels \u2014 how far to move the whole group",
                      "type": "number"
                    },
                    "offsetY": {
                      "description": "offsetY in canvas pixels \u2014 how far to move the whole group",
                      "type": "number"
                    },
                    "scaleX": {
                      "description": "scaleX multiplier (1 = unscaled), about the group's anchor",
                      "type": "number"
                    },
                    "scaleY": {
                      "description": "scaleY multiplier (1 = unscaled), about the group's anchor",
                      "type": "number"
                    },
                    "rotation": {
                      "description": "rotation in degrees",
                      "type": "number"
                    },
                    "rotateX": {
                      "description": "rotateX in degrees",
                      "type": "number"
                    },
                    "rotateY": {
                      "description": "rotateY in degrees",
                      "type": "number"
                    },
                    "opacity": {
                      "description": "0-100, multiplied over each child's own opacity",
                      "type": "number"
                    },
                    "blur": {
                      "description": "blur in pixels, applied over every child",
                      "type": "number"
                    },
                    "interpolation": {
                      "description": "Easing for THIS entry, applied to every property in it (default easeInOut).",
                      "type": "string",
                      "enum": [
                        "linear",
                        "easeInOut",
                        "easeIn",
                        "easeOut",
                        "sigmoid",
                        "smootherstep",
                        "smoothstep",
                        "hold",
                        "easeInBack",
                        "easeOutBack",
                        "easeInOutBack",
                        "custom"
                      ]
                    },
                    "interpolation_overrides": {
                      "description": "Per-property easing override, e.g. {\"opacity\":\"linear\"}.",
                      "type": "object",
                      "propertyNames": {
                        "type": "string",
                        "enum": [
                          "offsetX",
                          "offsetY",
                          "scaleX",
                          "scaleY",
                          "rotation",
                          "rotateX",
                          "rotateY",
                          "opacity",
                          "blur"
                        ]
                      },
                      "additionalProperties": {
                        "type": "string",
                        "enum": [
                          "linear",
                          "easeInOut",
                          "easeIn",
                          "easeOut",
                          "sigmoid",
                          "smootherstep",
                          "smoothstep",
                          "hold",
                          "easeInBack",
                          "easeOutBack",
                          "easeInOutBack",
                          "custom"
                        ]
                      }
                    },
                    "bezier": {
                      "description": "Cubic-bezier [x1,y1,x2,y2]; used when interpolation is 'custom'.",
                      "minItems": 4,
                      "maxItems": 4,
                      "type": "array",
                      "items": {
                        "type": "number"
                      }
                    },
                    "bezier_overrides": {
                      "description": "Per-property cubic-bezier override.",
                      "type": "object",
                      "propertyNames": {
                        "type": "string",
                        "enum": [
                          "offsetX",
                          "offsetY",
                          "scaleX",
                          "scaleY",
                          "rotation",
                          "rotateX",
                          "rotateY",
                          "opacity",
                          "blur"
                        ]
                      },
                      "additionalProperties": {
                        "minItems": 4,
                        "maxItems": 4,
                        "type": "array",
                        "items": {
                          "type": "number"
                        }
                      }
                    }
                  },
                  "required": [
                    "timestamp"
                  ],
                  "additionalProperties": false
                }
              }
            },
            "required": [
              "clip_index",
              "group_id"
            ],
            "additionalProperties": false
          },
          "description": "One entry per group to update"
        },
        "returning": {
          "description": "What comes back, the way get_clip's `select` shapes a read. Omitted: ids, the measured layout and the clip audit in compact form. 'ids': element_id per result. 'layout': the measured text fit (font_size_px, text_width_px, natural_width_px, line_count, fits_width/fits_height/fits \u2014 widen a box to natural_width_px, not text_width_px; an `adjusted` entry when the measured line box replaced your height) plus `placement` {y_anchor, top, center, bottom}: compare placement.center with a container's centre line to verify centring. 'qa.codes': the clip audit with severity/code/element_ids per finding; 'qa': with the message and fix on each. Errors always come back: in full under ['ids'], as {clip_index, errors: [{code, element_ids}]} under []; bleed under a keyframed group is a warning marked camera_move. A failed item and font_warnings always come back. [] is the leanest write: {total, succeeded, failed} plus errors.",
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "ids",
              "layout",
              "qa",
              "qa.codes"
            ]
          }
        }
      },
      "required": [
        "project_id",
        "items"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "remove_from_project": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "string",
          "description": "The project ID"
        },
        "target": {
          "type": "string",
          "enum": [
            "element",
            "group",
            "audio"
          ],
          "description": "What to remove: 'element', 'group' or 'audio'"
        },
        "clip_index": {
          "description": "Zero-based clip index. Required for target='group'; for target='element' an optional hint \u2014 the element is found by id either way",
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991
        },
        "element_id": {
          "description": "Element ID to remove (target='element'; or use element_ids)",
          "type": "string"
        },
        "element_ids": {
          "description": "Several element IDs to remove (target='element'). Removed one at a time, so a failure part-way leaves the earlier ones removed; retrying the same list is safe \u2014 ids already gone come back as already_absent. For a large batch prefer remove_elements, which lands in one save",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "music_id": {
          "description": "Music/SFX track ID to remove (required for target='audio')",
          "type": "string"
        },
        "group_id": {
          "description": "Group ID to remove (required for target='group')",
          "type": "string"
        },
        "keep_children": {
          "description": "target='group' only. Omit/true ungroups: children rise to the removed group's own parent. false deletes the whole subtree.",
          "type": "boolean"
        }
      },
      "required": [
        "project_id",
        "target"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "add_audio": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "string",
          "description": "The project ID"
        },
        "type": {
          "type": "string",
          "enum": [
            "music",
            "sfx"
          ],
          "description": "Audio type: 'music' or 'sfx'"
        },
        "src": {
          "description": "Audio source. For type='music' (required) or a stock type='sfx': the `src` from a find(type='music'|'sfx') result (may be an opaque Freesound handle \u2014 pass as-is), or an upload id (mcp_upload_id 'mup_\u2026' or a Clueso upload_id). Not needed for legacy fixed-library sfx (use sfx_key).",
          "type": "string"
        },
        "name": {
          "description": "Track/effect name. From the find result; for an upload it defaults to the file name.",
          "type": "string"
        },
        "source_duration": {
          "description": "Source duration in seconds, from the find result. Uploads and other URLs are probed.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "guide_start_time": {
          "description": "When to start playing in the guide timeline (seconds). Required for type='sfx', optional for music (default 0).",
          "type": "number",
          "minimum": 0
        },
        "guide_end_time": {
          "description": "When to stop playing (seconds). Music only. Default = end of project AT CALL TIME \u2014 captured once at insert and NOT recomputed when clips are later added or resized. Pass an explicit value (e.g. the intended final project duration) when adding audio before clips are sized.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "music_start_time": {
          "description": "In-point within the SOURCE track (seconds) \u2014 trims where playback begins in the music file. Music only. Default 0 (start of source).",
          "type": "number",
          "minimum": 0
        },
        "music_end_time": {
          "description": "Out-point within the SOURCE track (seconds) \u2014 trims where playback ends in the music file. Music only. Default = source_duration.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "volume": {
          "description": "Volume as percent (0\u2013100). Default: 20 for music (under narration), 80 for sfx.",
          "type": "number",
          "minimum": 0,
          "maximum": 100
        },
        "loop": {
          "description": "Loop the track if project is longer than the source. Music only. (default: false)",
          "type": "boolean"
        },
        "fade_in": {
          "description": "Fade-in duration in seconds. Music only. (default: 0.5)",
          "type": "number",
          "minimum": 0
        },
        "fade_out": {
          "description": "Fade-out duration in seconds. Music only. (default: 0.5)",
          "type": "number",
          "minimum": 0
        },
        "sfx_key": {
          "description": "LEGACY fixed-library SFX key (e.g. 'whoosh', 'pop', 'click'). Optional alternative to src for type='sfx'. Prefer the src/name/source_duration from a find(type='sfx') result.",
          "type": "string"
        }
      },
      "required": [
        "project_id",
        "type"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "update_audio": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "string",
          "description": "The project ID"
        },
        "music_id": {
          "type": "string",
          "description": "ID of the audio track (from get_project.audio_tracks[].id)"
        },
        "guide_start_time": {
          "description": "New start time on the project timeline (seconds)",
          "type": "number",
          "minimum": 0
        },
        "guide_end_time": {
          "description": "New end time on the project timeline (seconds)",
          "type": "number",
          "minimum": 0
        },
        "music_start_time": {
          "description": "New start offset within the source file (seconds)",
          "type": "number",
          "minimum": 0
        },
        "music_end_time": {
          "description": "New end offset within the source file (seconds)",
          "type": "number",
          "minimum": 0
        },
        "volume": {
          "description": "New playback volume 0-100",
          "type": "number",
          "minimum": 0,
          "maximum": 100
        },
        "loop": {
          "description": "Whether the track loops",
          "anyOf": [
            {
              "type": "boolean"
            },
            {
              "type": "string",
              "enum": [
                "0",
                "1",
                "true",
                "false",
                "yes",
                "no"
              ]
            }
          ]
        },
        "fade_in": {
          "description": "Fade-in duration (seconds)",
          "type": "number",
          "minimum": 0
        },
        "fade_out": {
          "description": "Fade-out duration (seconds)",
          "type": "number",
          "minimum": 0
        }
      },
      "required": [
        "project_id",
        "music_id"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "extract_audio": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "string",
          "description": "The project ID"
        },
        "clip_index": {
          "description": "Zero-based clip index. Ignored if clip_id is provided.",
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991
        },
        "clip_id": {
          "description": "Clip ID (preferred over clip_index).",
          "type": "string"
        },
        "restore": {
          "description": "true = undo: the clip plays its own audio again at 100%.",
          "anyOf": [
            {
              "type": "boolean"
            },
            {
              "type": "string",
              "enum": [
                "0",
                "1",
                "true",
                "false",
                "yes",
                "no"
              ]
            }
          ]
        }
      },
      "required": [
        "project_id"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "voiceover_batch": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "string",
          "description": "Project ID"
        },
        "entries": {
          "description": "Voiceover entries \u2014 at least one.",
          "minItems": 1,
          "type": "array",
          "items": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "action": {
                    "type": "string",
                    "const": "set_text",
                    "description": "Write the voiceover transcript onto the clip without generating audio."
                  },
                  "clip_index": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991,
                    "description": "Zero-based clip index"
                  },
                  "text": {
                    "type": "string",
                    "minLength": 1,
                    "description": "Voiceover transcript. Emotion and audio tags in square brackets ([excited], [whispers], [laughs]) are performed, not spoken. <pause:1.5s> is exact timed silence, allowed at the very start (a lead-in) and end (a hold)."
                  }
                },
                "required": [
                  "action",
                  "clip_index",
                  "text"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "action": {
                    "type": "string",
                    "const": "generate_speech",
                    "description": "Generate speech audio from the transcript already set on the clip."
                  },
                  "clip_index": {
                    "description": "Zero-based clip index. Give this or clip_id.",
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991
                  },
                  "clip_id": {
                    "description": "Clip ID. Give this or clip_index.",
                    "type": "string",
                    "minLength": 1
                  },
                  "language": {
                    "description": "Language of this clip's speech, e.g. 'de' (default 'en').",
                    "type": "string",
                    "minLength": 2
                  }
                },
                "required": [
                  "action"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "action": {
                    "type": "string",
                    "const": "set_and_generate",
                    "description": "Write the transcript and generate its speech audio in one step."
                  },
                  "clip_index": {
                    "description": "Zero-based clip index. Give this or clip_id.",
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991
                  },
                  "clip_id": {
                    "description": "Clip ID. Give this or clip_index.",
                    "type": "string",
                    "minLength": 1
                  },
                  "text": {
                    "type": "string",
                    "minLength": 1,
                    "description": "Voiceover transcript. Emotion and audio tags in square brackets ([excited], [whispers], [laughs]) are performed, not spoken. <pause:1.5s> is exact timed silence, allowed at the very start (a lead-in) and end (a hold)."
                  },
                  "language": {
                    "description": "Language of this clip's speech, e.g. 'de' (default 'en').",
                    "type": "string",
                    "minLength": 2
                  }
                },
                "required": [
                  "action",
                  "text"
                ]
              }
            ]
          }
        },
        "wait": {
          "description": "true blocks until the speech this call starts has landed, so the clip durations, voiceover_words and element times you read next are the real ones: `ready` lists each landed clip with its new duration. Bounded by wait_seconds; clips still generating then come back under durations_pending (poll get_clip(select:['busy']) for those). Use it whenever the next step times anything to the voiceover. Inside run_script the wait counts against the script's time.",
          "type": "boolean"
        },
        "wait_seconds": {
          "description": "With wait: the most to wait, default 50s.",
          "type": "integer",
          "minimum": 1,
          "maximum": 55
        }
      },
      "required": [
        "project_id",
        "entries"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "auto_sync": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "string",
          "description": "Project ID."
        },
        "clip_id": {
          "type": "string",
          "description": "Clip ID to auto-sync."
        }
      },
      "required": [
        "project_id",
        "clip_id"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "add_sync_point": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "string",
          "description": "Project ID."
        },
        "clip_id": {
          "description": "Clip ID (preferred). If omitted, clip_index is used.",
          "type": "string"
        },
        "clip_index": {
          "description": "Zero-based clip index. Ignored if clip_id is provided.",
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991
        },
        "word": {
          "type": "string",
          "minLength": 1,
          "description": "Target word (case-insensitive; trailing punctuation is stripped)."
        },
        "occurrence": {
          "default": 1,
          "description": "1-indexed match number when the word repeats. Defaults to 1 (first occurrence).",
          "type": "integer",
          "minimum": 1,
          "maximum": 9007199254740991
        },
        "timestamp_seconds": {
          "type": "number",
          "minimum": 0,
          "description": "Clip-relative timestamp in seconds. Auto-mapped to original-recording seconds when the clip has generated_timestamps."
        },
        "sync_point_name": {
          "description": "Optional label (e.g. 'Click submit'). Defaults to 'Sync point N' using the next order number.",
          "type": "string"
        }
      },
      "required": [
        "project_id",
        "word",
        "timestamp_seconds"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "upload_file": {
      "type": "object",
      "properties": {
        "files": {
          "description": "ChatGPT file attachments, one entry per file (filled by ChatGPT).",
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "download_url": {
                "type": "string",
                "description": "Temporary download URL supplied by ChatGPT"
              },
              "file_id": {
                "type": "string",
                "description": "ChatGPT file id"
              },
              "mime_type": {
                "type": "string"
              },
              "file_name": {
                "type": "string"
              }
            },
            "required": [
              "download_url",
              "file_id"
            ]
          }
        },
        "file_url": {
          "description": "Public URL to fetch the file from",
          "type": "string"
        },
        "file_name": {
          "description": "File name with extension. Returns a Clueso upload URL + curl command that streams this single local file to us (single file).",
          "type": "string"
        },
        "request_hosted_upload": {
          "description": "If true, returns a hosted upload page. Call once for all files \u2014 the page accepts multiple uploads under one token.",
          "type": "boolean"
        },
        "purpose": {
          "description": "'font': add the file to this workspace's brand fonts once it arrives (see above). With request_hosted_upload, every file dropped on the page is one, reported by check_uploads.",
          "type": "string",
          "enum": [
            "font"
          ]
        },
        "display_name": {
          "description": "With purpose 'font' and one file (file_name, file_url, one files entry, or one file_names entry): the name to list the font under. Default: its family name without build dates or versions.",
          "type": "string",
          "minLength": 1,
          "maxLength": 100
        },
        "file_names": {
          "description": "Hosted mode: the file names the user will upload, shown on the page. Without request_hosted_upload: upload these local files in one call (one token + URL each, and one curl for all), max 50.",
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      },
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "check_uploads": {
      "type": "object",
      "properties": {
        "upload_token": {
          "type": "string",
          "description": "The upload_token returned by upload_file(request_hosted_upload: true)"
        }
      },
      "required": [
        "upload_token"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "generate_media": {
      "type": "object",
      "properties": {
        "kind": {
          "type": "string",
          "enum": [
            "image",
            "animation"
          ],
          "description": "'image' = AI text-to-image; 'animation' = canvas-code-based motion graphic."
        },
        "project_id": {
          "type": "string",
          "description": "Project ID."
        },
        "clip_id": {
          "type": "string",
          "description": "Clip ID to place the generated element into."
        },
        "prompt": {
          "type": "string",
          "minLength": 1,
          "description": "Generation prompt. For animations, be SPECIFIC: name the UI elements, interaction sequence, timing feel, and visual style. Vague prompts produce bad output."
        },
        "x": {
          "type": "number",
          "description": "Top-left X in canvas pixels."
        },
        "y": {
          "type": "number",
          "description": "Top-left Y in canvas pixels."
        },
        "width": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Width in pixels."
        },
        "height": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Height in pixels."
        },
        "start_time": {
          "description": "Appear at (seconds).",
          "type": "number",
          "minimum": 0
        },
        "end_time": {
          "description": "Disappear at (seconds).",
          "type": "number",
          "minimum": 0
        },
        "style_id": {
          "description": "Image only. Style preset ID from find(type='image_gen_style_packs'). See resource clueso://docs/generation-styles.",
          "type": "string"
        },
        "model": {
          "description": "Image only. Which image model renders it. Default `gemini-3-pro-image` (Nano Banana Pro), a strong general choice \u2014 leave it off unless you want one of the specifics below. `gemini-3.1-flash-image` (Nano Banana 2), `gemini-nano-banana-2.1` (Nano Banana 2.1, Google's newest), `gemini-3.1-flash-lite-image` (Nano Banana 2 Lite, the fastest and cheapest) and `gemini-2.5-flash-image` are faster and cheaper. `gpt-image-2.5-flare` is OpenAI's fast one, `gpt-image-2.5-sunburst` its most precise editor, and `gpt-image-2` is the one to reach for when the image must carry legible text or follow several reference images. `gpt-image-1` / `gpt-image-1.5` are the older pair and only accept square-ish framing \u2014 a wide box is snapped to 4:3 rather than honoured, so do not pick them for a banner. An unlisted value is rejected here rather than silently swapped for the default, which is what the backend does with one.",
          "type": "string",
          "enum": [
            "gemini-3-pro-image",
            "gemini-3.1-flash-image",
            "gemini-nano-banana-2.1",
            "gemini-3.1-flash-lite-image",
            "gemini-2.5-flash-image",
            "gpt-image-2.5-flare",
            "gpt-image-2.5-sunburst",
            "gpt-image-2",
            "gpt-image-1.5",
            "gpt-image-1"
          ]
        },
        "background": {
          "description": "Image only, and only honoured by `gpt-image-2.5-flare`, `gpt-image-2.5-sunburst`, `gpt-image-1` and `gpt-image-1.5` \u2014 every other model is always opaque and ignores this. Pass `false` for a cut-out with no background: icons, logos, stickers, anything meant to sit ON the composition rather than behind it. You MUST set `model` to one of those four in the same call; `gpt-image-2.5-flare` is the one to default to, since the older pair cannot frame wide. Asking for transparency in the PROMPT instead does not work \u2014 the model paints a grey-and-white checkerboard as real pixels.",
          "anyOf": [
            {
              "type": "boolean"
            },
            {
              "type": "string",
              "enum": [
                "0",
                "1",
                "true",
                "false",
                "yes",
                "no"
              ]
            }
          ]
        },
        "aspect_ratio": {
          "description": "Image only. Overrides the ratio inferred from width/height. Leave it off and the box decides, which is usually what you want \u2014 set it when the box is a placeholder and you know the shape you need. Still clamped to what the model accepts: gpt-image-1/1.5 only do 1:1, 4:3 and 3:4; the gpt-image-2.5 pair adds 16:9 and 9:16; gpt-image-2 has no 3:2 or 2:3, so those become 4:3 and 3:4. The response does not report the ratio used.",
          "type": "string",
          "enum": [
            "1:1",
            "4:3",
            "3:4",
            "16:9",
            "9:16",
            "3:2",
            "2:3"
          ]
        },
        "base_component_id": {
          "description": "Animation only. Reuse a saved animation component as the starting point (from find(type='element_components')). To re-skin its tunable parameters, set parameter_values via update_elements after it renders.",
          "type": "string"
        },
        "voiceover_text": {
          "description": "Animation only. Paces the motion to the spoken script \u2014 and as a side effect sets this clip's voiceover text and triggers speech generation for the clip. If the clip's current voiceover_text (get_clip) has <SYNC_MARKER N> tokens, this text must carry each of them once, in order, or the animation is refused.",
          "type": "string"
        },
        "reference_image_url": {
          "description": "Public URL of a reference image. Mutually exclusive with mcp_upload_id.",
          "type": "string"
        },
        "mcp_upload_id": {
          "description": "mcp_upload_id from upload_file, or a Clueso upload_id. Resolved server-side to a presigned URL before generation.",
          "type": "string"
        },
        "animation_setting": {
          "description": "Image only. Entry/exit animation, same shape as add_elements' type_data.animation_setting. The design guide asks AI images to enter with `masked_reveal` or a slow fade, so set it here rather than following up with update_elements \u2014 that follow-up targets the element while it is still generating. Ignored for kind='animation', which animates through its generated code.",
          "type": "object",
          "properties": {
            "entry": {
              "description": "Entry (appear) animation phase.",
              "type": "object",
              "properties": {
                "preset": {
                  "description": "Preset for this phase. 'typewriter' is char-only and has no direction.",
                  "type": "string",
                  "enum": [
                    "slide",
                    "fade",
                    "pop",
                    "scale",
                    "masked_reveal",
                    "typewriter",
                    "none"
                  ]
                },
                "apply_to_unit": {
                  "description": "Granularity. 'typewriter' is restricted to 'char'.",
                  "type": "string",
                  "enum": [
                    "char",
                    "word",
                    "line",
                    "box"
                  ]
                },
                "duration_per_unit_ms": {
                  "description": "NAME IS MISLEADING \u2014 this is the TOTAL duration of the phase in ms (NOT per unit). The renderer splits it across all N units with ~50% overlap. For a perceptible per-unit rhythm scale UP with the unit count (e.g. typewriter on N chars \u2248 N \u00d7 100\u2013200 ms). For applyToUnit='box' it's just the phase duration. Omit it and you get 400ms, the same default the editor creates with.",
                  "type": "number",
                  "minimum": 0
                },
                "direction": {
                  "description": "Slide direction for this phase. Ignored by fade/scale/pop/typewriter; pass 'none' when the preset has no direction.",
                  "type": "string",
                  "enum": [
                    "left",
                    "right",
                    "top",
                    "bottom",
                    "center_all",
                    "center_vertically",
                    "center_horizontally",
                    "none"
                  ]
                }
              }
            },
            "exit": {
              "description": "Exit (disappear) animation phase.",
              "type": "object",
              "properties": {
                "preset": {
                  "description": "Preset for this phase. 'typewriter' is char-only and has no direction.",
                  "type": "string",
                  "enum": [
                    "slide",
                    "fade",
                    "pop",
                    "scale",
                    "masked_reveal",
                    "typewriter",
                    "none"
                  ]
                },
                "apply_to_unit": {
                  "description": "Granularity. 'typewriter' is restricted to 'char'.",
                  "type": "string",
                  "enum": [
                    "char",
                    "word",
                    "line",
                    "box"
                  ]
                },
                "duration_per_unit_ms": {
                  "description": "NAME IS MISLEADING \u2014 this is the TOTAL duration of the phase in ms (NOT per unit). The renderer splits it across all N units with ~50% overlap. For a perceptible per-unit rhythm scale UP with the unit count (e.g. typewriter on N chars \u2248 N \u00d7 100\u2013200 ms). For applyToUnit='box' it's just the phase duration. Omit it and you get 400ms, the same default the editor creates with.",
                  "type": "number",
                  "minimum": 0
                },
                "direction": {
                  "description": "Slide direction for this phase. Ignored by fade/scale/pop/typewriter; pass 'none' when the preset has no direction.",
                  "type": "string",
                  "enum": [
                    "left",
                    "right",
                    "top",
                    "bottom",
                    "center_all",
                    "center_vertically",
                    "center_horizontally",
                    "none"
                  ]
                }
              }
            }
          }
        }
      },
      "required": [
        "kind",
        "project_id",
        "clip_id",
        "prompt",
        "x",
        "y",
        "width",
        "height"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "additionalProperties": {}
    },
    "analyze_audio": {
      "type": "object",
      "properties": {
        "mcp_upload_id": {
          "description": "mcp_upload_id from upload_file, or a Clueso upload_id. Use this OR file_url, not both.",
          "type": "string"
        },
        "file_url": {
          "description": "Public http(s) URL to the audio. Use when the file is already accessible (e.g. find(type='music') result, Clueso CDN). Validated against private/loopback ranges.",
          "type": "string"
        },
        "mode": {
          "type": "string",
          "enum": [
            "transcript",
            "silences",
            "beats",
            "features",
            "energy",
            "sections",
            "describe"
          ],
          "description": "Which analysis to run."
        },
        "range_start_ms": {
          "description": "Start of analysis window in milliseconds. Default 0 (beginning).",
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991
        },
        "range_end_ms": {
          "description": "End of analysis window in milliseconds. Default: whole file.",
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991
        },
        "transcript_options": {
          "description": "Options for mode='transcript'.",
          "type": "object",
          "properties": {
            "language": {
              "description": "Language code hint (e.g. 'en', 'es'). Auto-detected if omitted.",
              "type": "string"
            }
          }
        },
        "silences_options": {
          "description": "Options for mode='silences'.",
          "type": "object",
          "properties": {
            "min_pause_ms": {
              "description": "Minimum pause length to report. Default 250ms.",
              "type": "integer",
              "exclusiveMinimum": 0,
              "maximum": 9007199254740991
            },
            "threshold_db": {
              "description": "Silence threshold in dBFS. Default -40.",
              "type": "number"
            }
          }
        },
        "energy_options": {
          "description": "Options for mode='energy'.",
          "type": "object",
          "properties": {
            "samples_per_second": {
              "description": "Loudness values per second. Default 4.",
              "type": "number",
              "minimum": 1,
              "maximum": 20
            }
          }
        }
      },
      "required": [
        "mode"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "get_article": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "string",
          "description": "Project ID"
        }
      },
      "required": [
        "project_id"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "update_article": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "string",
          "description": "Project ID"
        },
        "markdown": {
          "type": "string",
          "description": "Updated article content as markdown with <SCREENSHOT {id}> placeholders"
        },
        "article_version": {
          "description": "The article_version of the article this markdown was written from. Recommended: without it, an edit someone else made since is overwritten.",
          "type": "string"
        }
      },
      "required": [
        "project_id",
        "markdown"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "add_article_media": {
      "type": "object",
      "properties": {
        "kind": {
          "type": "string",
          "enum": [
            "image",
            "gif"
          ],
          "description": "'image' or 'gif'."
        },
        "project_id": {
          "type": "string",
          "description": "Project ID."
        },
        "timestamp": {
          "description": "kind='image': capture frame at this timestamp (seconds). kind='gif': GIF start time.",
          "type": "number",
          "minimum": 0
        },
        "duration": {
          "description": "kind='gif' only: GIF duration in seconds.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "mcp_upload_id": {
          "description": "kind='image' only: attach an uploaded image (mcp_upload_id from upload_file, or a Clueso upload_id) instead of capturing from video.",
          "type": "string"
        },
        "source_url": {
          "description": "kind='image' only: an https image URL to download and attach.",
          "type": "string",
          "format": "uri"
        },
        "prompt": {
          "description": "kind='image' only: generate an AI illustration from this prompt instead of capturing or attaching one.",
          "type": "string",
          "minLength": 1
        },
        "style_id": {
          "description": "kind='image' with prompt only: an image style pack ID from find(type='image_gen_style_packs').",
          "type": "string"
        },
        "aspect_ratio": {
          "description": "kind='image' with prompt only: the illustration's shape. Default 16:9.",
          "type": "string",
          "enum": [
            "16:9",
            "1:1",
            "4:3",
            "3:4",
            "9:16",
            "21:9"
          ]
        },
        "after": {
          "description": "Put it right after the block holding this screenshot_id, or 'start' / 'end' (the default).",
          "type": "string",
          "minLength": 1
        },
        "replace": {
          "description": "Put it in place of this screenshot_id, wherever that sits; the old one is removed from the article.",
          "type": "string",
          "minLength": 1
        }
      },
      "required": [
        "kind",
        "project_id"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "update_article_media": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "string",
          "description": "Project ID."
        },
        "screenshot_id": {
          "type": "string",
          "description": "Screenshot ID (from get_article)."
        },
        "zoom_factor": {
          "description": "Zoom magnification (1.0 = no zoom).",
          "type": "number",
          "minimum": 1
        },
        "crop": {
          "description": "Crop data: { position: [x,y], size: [w,h] } in fractional coords.",
          "type": "object",
          "propertyNames": {
            "type": "string"
          },
          "additionalProperties": {}
        },
        "shapes": {
          "description": "Array of shape objects for annotations.",
          "type": "array",
          "items": {
            "type": "object",
            "propertyNames": {
              "type": "string"
            },
            "additionalProperties": {}
          }
        },
        "render": {
          "description": "If true (and no other updates supplied), returns current details and a PNG preview without mutating.",
          "type": "boolean"
        }
      },
      "required": [
        "project_id",
        "screenshot_id"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "create_clueprint": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "description": "Name of the clueprint"
        },
        "description": {
          "description": "Description of what this clueprint does",
          "type": "string"
        },
        "visibility": {
          "type": "string",
          "enum": [
            "private",
            "workspace"
          ],
          "description": "'workspace' shares it with your workspace; 'private' is stored as 'workspace'."
        },
        "source_guide_ids": {
          "description": "IDs of existing projects to extract template data from",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "brand_locked": {
          "description": "true: projects in this workspace built from this clueprint keep its own colors, fonts and logos instead of their brand kit's. Default false (re-skinned to the project's kit).",
          "type": "boolean"
        }
      },
      "required": [
        "name",
        "visibility"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "get_clueprint": {
      "type": "object",
      "properties": {
        "clueprint_id": {
          "type": "string",
          "description": "ID of the clueprint."
        },
        "include": {
          "default": "tree",
          "description": "What to return: 'metadata' | 'tree' | 'files' | 'all'. Defaults to 'tree'.",
          "type": "string",
          "enum": [
            "metadata",
            "tree",
            "files",
            "all"
          ]
        },
        "file_paths": {
          "description": "include='files' only: relative paths to read (e.g. ['design/colors.md', 'rules/voice.md']). Ignored for other modes.",
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "use": {
          "description": "true: you are building a project from this clueprint; counts the use. Once per build.",
          "type": "boolean"
        },
        "project_id": {
          "description": "use=true only: the project being built from it, when it exists.",
          "type": "string"
        }
      },
      "required": [
        "clueprint_id"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "update_clueprint": {
      "type": "object",
      "properties": {
        "clueprint_id": {
          "type": "string",
          "description": "ID of the clueprint to update."
        },
        "name": {
          "description": "New name.",
          "type": "string"
        },
        "description": {
          "description": "New description.",
          "type": "string"
        },
        "tags": {
          "description": "New tags (replaces existing).",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "visibility": {
          "description": "New visibility.",
          "type": "string",
          "enum": [
            "private",
            "workspace"
          ]
        },
        "thumbnail_path": {
          "description": "Relative path of a screenshot in the clueprint to use as thumbnail (e.g. 'screenshots/title-slide.png').",
          "type": "string"
        },
        "brand_locked": {
          "description": "true: projects built from it keep its own colors, fonts and logos; false: re-skinned to each project's brand kit.",
          "type": "boolean"
        },
        "files": {
          "description": "File mutations \u2014 writes and deletes. Empty/omitted for metadata-only updates.",
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "path": {
                "type": "string",
                "description": "Relative file path (e.g. 'design/colors.md', 'screenshots/intro.png')."
              },
              "content": {
                "description": "Text content, or base64-encoded binary if encoding='base64'. Set to null to DELETE this path. Omit (with source_url) when writing from a URL.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "encoding": {
                "description": "Set to 'base64' for binary files like images.",
                "type": "string",
                "enum": [
                  "base64"
                ]
              },
              "source_url": {
                "description": "Presigned URL to download the file from. Server fetches and saves it. Use this instead of inline content for large/binary files.",
                "type": "string"
              }
            },
            "required": [
              "path"
            ]
          }
        },
        "decisions": {
          "description": "Build-time decisions to record (see the description).",
          "type": "object",
          "properties": {
            "slide_count_strategy": {
              "type": "object",
              "properties": {
                "basis": {
                  "type": "string",
                  "enum": [
                    "one_body_slide_per_user_upload",
                    "one_body_slide_per_heading",
                    "one_body_slide_per_section",
                    "one_body_slide_per_h1",
                    "fixed_count",
                    "ask_per_build",
                    "user_override"
                  ]
                },
                "target_count": {
                  "description": "fixed_count: body slides",
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 9007199254740991
                },
                "source": {
                  "type": "string",
                  "enum": [
                    "inferred",
                    "user_confirmed",
                    "user_override"
                  ]
                }
              },
              "required": [
                "basis"
              ]
            },
            "animation_modes": {
              "description": "{animation_id: mode}; user_uploaded = a logo animation the user uploads at build",
              "type": "object",
              "propertyNames": {
                "type": "string"
              },
              "additionalProperties": {
                "type": "string",
                "enum": [
                  "reuse",
                  "reuse_and_modify",
                  "user_uploaded"
                ]
              }
            },
            "image_modes": {
              "description": "{image_id: mode}",
              "type": "object",
              "propertyNames": {
                "type": "string"
              },
              "additionalProperties": {
                "type": "string",
                "enum": [
                  "reuse",
                  "reuse_and_modify",
                  "screenshot_from_guide",
                  "user_uploaded"
                ]
              }
            },
            "image_modes_by_role": {
              "description": "{role: {purpose_cluster: mode}}",
              "type": "object",
              "propertyNames": {
                "type": "string"
              },
              "additionalProperties": {
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "type": "string",
                  "enum": [
                    "reuse",
                    "reuse_and_modify",
                    "screenshot_from_guide",
                    "user_uploaded"
                  ]
                }
              }
            },
            "voiceover_policies": {
              "description": "{source_clip_id: policy}",
              "type": "object",
              "propertyNames": {
                "type": "string"
              },
              "additionalProperties": {
                "type": "string",
                "enum": [
                  "retain_silent",
                  "generate"
                ]
              }
            },
            "voiceover_policies_by_role": {
              "description": "{role: policy}",
              "type": "object",
              "propertyNames": {
                "type": "string"
              },
              "additionalProperties": {
                "type": "string",
                "enum": [
                  "retain_silent",
                  "generate"
                ]
              }
            },
            "split_decision": {
              "type": "object",
              "properties": {
                "decision": {
                  "type": "string",
                  "enum": [
                    "keep_as_one",
                    "optimal_at_creation",
                    "user_instruction"
                  ]
                },
                "instruction": {
                  "description": "Required for user_instruction",
                  "type": "string"
                }
              },
              "required": [
                "decision"
              ]
            },
            "allowed_element_types": {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "clip_type": {
                    "type": "string",
                    "enum": [
                      "slide_clip",
                      "video_clip"
                    ]
                  },
                  "allowed": {
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "zoom",
                        "spotlight",
                        "callout",
                        "rectangle"
                      ]
                    }
                  },
                  "user_added_rules": {
                    "description": "{type: rule}",
                    "type": "object",
                    "propertyNames": {
                      "type": "string"
                    },
                    "additionalProperties": {
                      "type": "string"
                    }
                  }
                },
                "required": [
                  "clip_type",
                  "allowed"
                ]
              }
            },
            "finalize_layout_texts": {
              "type": "boolean",
              "const": true
            }
          },
          "additionalProperties": false
        }
      },
      "required": [
        "clueprint_id"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "manage_brand_kit": {
      "type": "object",
      "properties": {
        "action": {
          "type": "string",
          "enum": [
            "create",
            "preview_fill",
            "fill",
            "update",
            "set_default",
            "research",
            "dark_logo",
            "delete"
          ]
        },
        "kit_id": {
          "description": "The kit; every action but create.",
          "type": "string"
        },
        "url": {
          "description": "Website, e.g. 'cursor.com' (create, preview_fill, fill).",
          "type": "string"
        },
        "name": {
          "description": "Kit name (create).",
          "type": "string"
        },
        "replace": {
          "description": "fill: sections that take the site's version; 'palette' is the same as 'colors'.",
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "company",
              "logos",
              "colors",
              "fonts",
              "icons",
              "reference_images",
              "guidelines",
              "palette"
            ]
          }
        },
        "add": {
          "description": "fill: logos and/or reference_images to add beside the kit's own.",
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "logos",
              "reference_images"
            ]
          }
        },
        "changes": {
          "description": "update: the fields to change.",
          "type": "object",
          "propertyNames": {
            "type": "string"
          },
          "additionalProperties": {}
        },
        "logo_id": {
          "description": "dark_logo: the kit's light-background logo to make it from.",
          "type": "string"
        },
        "mode": {
          "description": "dark_logo: how to make it.",
          "type": "string",
          "enum": [
            "white",
            "recolor"
          ]
        },
        "confirmed": {
          "description": "delete: true only after the user agreed to delete this kit.",
          "type": "boolean"
        }
      },
      "required": [
        "action"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "set_voice": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "string",
          "description": "The project (guide) ID"
        },
        "voice_name": {
          "type": "string",
          "minLength": 1,
          "description": "Voice name exactly as find(type='voices') lists it (e.g. 'Alex')."
        },
        "voice_engine": {
          "type": "string",
          "minLength": 1,
          "description": "Voice engine \u2014 'eleven', 'cartesia', 'google', etc."
        },
        "voice_version": {
          "description": "ElevenLabs voice generation. Leave unset: latest, except a voice the workspace saved keeps its saved version. Use 'legacy' ONLY when the user explicitly asks for the legacy/older voice generation; never choose it yourself. Ignored for non-ElevenLabs voices.",
          "type": "string",
          "enum": [
            "latest",
            "legacy"
          ]
        },
        "clip_ids": {
          "description": "Clip ids to change the voice on. Omit to change EVERY clip in the project.",
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      },
      "required": [
        "project_id",
        "voice_name",
        "voice_engine"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "pronunciation": {
      "type": "object",
      "properties": {
        "action": {
          "type": "string",
          "enum": [
            "list",
            "set",
            "remove",
            "to_ipa"
          ]
        },
        "word": {
          "description": "set/remove: the word as written in the script.",
          "type": "string"
        },
        "spelling": {
          "description": "set: the sound-alike respelling.",
          "type": "string"
        },
        "ipa": {
          "description": "set: IPA phonemes (slashes optional).",
          "type": "string"
        },
        "language": {
          "description": "Voice language code or name (default 'en'); an unknown one is an error.",
          "type": "string"
        },
        "case_sensitive": {
          "description": "set: match only this exact casing (default false).",
          "type": "boolean"
        },
        "text": {
          "description": "to_ipa: text to convert.",
          "type": "string"
        },
        "replace": {
          "description": "set: overwrite an existing entry, only after the user confirmed.",
          "type": "boolean"
        },
        "project_id": {
          "description": "The project being worked on: its open editors pick up the change.",
          "type": "string"
        }
      },
      "required": [
        "action"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "add_comment": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "string",
          "description": "The project (guide) ID"
        },
        "comment_text": {
          "type": "string",
          "minLength": 1,
          "description": "Comment text to post"
        },
        "clip_id": {
          "description": "Optional clip ID to pin the comment to. Omit for a project-level comment.",
          "type": "string"
        },
        "clip_timestamp": {
          "description": "Optional timestamp within the clip (seconds). Only used when clip_id is provided.",
          "type": "number",
          "minimum": 0
        },
        "parent_comment_id": {
          "description": "Reply to this comment instead of starting a new thread. Take the id from get_comments. Replying is how you answer feedback you have acted on \u2014 the clip pin and timestamp come from the parent, so leave clip_id/clip_timestamp off.",
          "type": "string"
        }
      },
      "required": [
        "project_id",
        "comment_text"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "get_comments": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "string",
          "description": "The project (guide) ID"
        },
        "clip_id": {
          "description": "Only comments pinned to this clip. Omit for every comment on the project, project-level ones included.",
          "type": "string"
        },
        "include_resolved": {
          "description": "Include threads already marked resolved (default false). Turn it on to see history; leave it off to see the work outstanding.",
          "type": "boolean"
        }
      },
      "required": [
        "project_id"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "record_screen": {
      "type": "object",
      "properties": {
        "guide_id": {
          "type": "string",
          "description": "Target guide ID"
        },
        "chat_id": {
          "type": "string",
          "description": "Conversation context ID"
        },
        "preceding_clip_id": {
          "type": "string",
          "description": "Clip ID after which to insert the new clip"
        },
        "custom_instructions": {
          "default": "",
          "description": "Optional per-recording instructions (e.g. \"select project X\", \"add rectangle 200x100\"). Not related to mocking.",
          "type": "string"
        },
        "language": {
          "default": "en",
          "description": "Language code for the recording (default: en)",
          "type": "string"
        },
        "voice_version": {
          "description": "ElevenLabs voice generation for every scene's voice. Leave unset: a named voice uses latest, while a voice the workspace saved and the workspace default keep their own saved version. Use 'legacy' ONLY when the user explicitly asks for the legacy/older voice generation; never choose it yourself.",
          "type": "string",
          "enum": [
            "latest",
            "legacy"
          ]
        },
        "exact_video_script": {
          "default": false,
          "description": "Legacy flag \u2014 prefer video_script_mode instead. Set to true when the video narration must be used exactly as written \u2014 the agent that does the recording will not reword, rephrase, or rewrite video_script at all. Default false.",
          "type": "boolean"
        },
        "exact_article_script": {
          "default": false,
          "description": "Legacy flag \u2014 prefer article_script_mode instead. Set to true when the article must be used exactly as written \u2014 the agent that does the recording will not reword, rephrase, or rewrite article_script at all. Default false.",
          "type": "boolean"
        },
        "video_script_mode": {
          "description": "How the narration is treated. 'exact': the user's narration is final and is read word for word; steps the script does not mention play silently. 'near_exact': keep the user's narration word for word, but briefly narrate steps the recording must take that the script does not mention (for example opening a settings dialog to reach something) \u2014 use this when the user gives a finished script and did not ask for strictly exact wording. 'rewrite': the narration is a draft and may be reworded to match what was recorded \u2014 use this when there is no user script or it is rough. Overrides exact_video_script when both are set.",
          "type": "string",
          "enum": [
            "rewrite",
            "near_exact",
            "exact"
          ]
        },
        "article_script_mode": {
          "description": "'exact': the article is used word for word. 'rewrite': the article may be reworded to match the recording. Overrides exact_article_script when both are set.",
          "type": "string",
          "enum": [
            "rewrite",
            "exact"
          ]
        },
        "scenes": {
          "minItems": 1,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "type": {
                "description": "Scene kind. 'narrated' carries a voiceover and requires narration_script; 'b-roll' is silent footage with no narration or article.",
                "type": "string",
                "enum": [
                  "narrated",
                  "b-roll"
                ]
              },
              "narration_script": {
                "description": "Voiceover text spoken over this scene. Required for a narrated scene, omitted for b-roll.",
                "type": "string"
              },
              "article_script": {
                "description": "Article text written into the written guide for this scene.",
                "type": "string"
              },
              "description": {
                "description": "What should happen on screen during this scene.",
                "type": "string"
              },
              "clip_name": {
                "description": "Title for the clip this scene produces.",
                "type": "string"
              },
              "exact_video_script": {
                "description": "Legacy flag \u2014 prefer video_script_mode instead. When true, narration_script for this scene is used verbatim and left unreworded. Overrides the top-level setting for this scene.",
                "type": "boolean"
              },
              "exact_article_script": {
                "description": "Legacy flag \u2014 prefer article_script_mode instead. When true, article_script for this scene is used verbatim and left unreworded. Overrides the top-level setting for this scene.",
                "type": "boolean"
              },
              "video_script_mode": {
                "description": "How the narration is treated for this scene. Overrides the top-level setting for this scene.",
                "type": "string",
                "enum": [
                  "rewrite",
                  "near_exact",
                  "exact"
                ]
              },
              "article_script_mode": {
                "description": "How the article is treated for this scene. Overrides the top-level setting for this scene.",
                "type": "string",
                "enum": [
                  "rewrite",
                  "exact"
                ]
              },
              "voice_name": {
                "description": "Voice to narrate this scene, from find(type='voices').",
                "type": "string"
              },
              "voice_engine": {
                "description": "Speech engine used to render this scene's voice.",
                "type": "string"
              },
              "pace": {
                "description": "b-roll scenes ONLY \u2014 set to null on narrated scenes by the normalizer, so it has no effect there. Controls how long the scene dwells ('quick' | 'normal' | 'hold'); absent means unset, not 'normal'.",
                "type": "string",
                "enum": [
                  "quick",
                  "normal",
                  "hold"
                ]
              },
              "entry": {
                "description": "How this scene begins relative to the preceding one.",
                "type": "object",
                "properties": {
                  "mode": {
                    "type": "string",
                    "enum": [
                      "fresh",
                      "continue"
                    ],
                    "description": "'fresh' (default) or 'continue' \u2014 how this scene begins relative to the previous one. Consumed by AVS (contract: MULTI_VIDEO_MODE_CONTRACT.md in gigauser-backend-prod)."
                  }
                },
                "required": [
                  "mode"
                ]
              },
              "exit": {
                "description": "How this scene ends.",
                "type": "object",
                "properties": {
                  "mode": {
                    "type": "string",
                    "enum": [
                      "hard",
                      "hold",
                      "freeze"
                    ],
                    "description": "'hard' (default), 'hold' or 'freeze' \u2014 how this scene ends. Consumed by AVS (contract: MULTI_VIDEO_MODE_CONTRACT.md in gigauser-backend-prod)."
                  }
                },
                "required": [
                  "mode"
                ]
              },
              "custom_instructions": {
                "description": "Extra guidance for recording this scene only, e.g. 'select project X' or 'scroll to the pricing table'.",
                "type": "string"
              }
            }
          },
          "description": "REQUIRED \u2014 the recording to make, as a list of scenes. This is the ONLY way to specify what to record. A normal single recording is exactly ONE scene; a code-wizard multi-scene / marketing video is N scenes (one clip per scene, and ALL cuts of one video go in a SINGLE record_screen call). A narrated scene REQUIRES a non-empty narration_script; a b-roll scene is silent (no narration, no article). A cut that starts somewhere disconnected is just a scene with entry.mode \"fresh\". Cleopatra orgs accept exactly ONE narrated scene. Do not set scene_id \u2014 it is assigned server-side."
        },
        "video_intention": {
          "default": "",
          "description": "One-line intent shared across all scenes of a multi-scene recording (e.g. \"punchy 30s launch teaser for feature X\"). Ignored for single-scene recordings.",
          "type": "string"
        },
        "recording_session_id": {
          "description": "EDIT an existing recording instead of shooting a new one. Pass the recording_session_id from the record_screen that made it, or read it off get_clip. The recorder restores that take's code, notes and click script and changes only what you ask for, which is far faster and cheaper than re-recording. Omit for a fresh recording. Only code-wizard recordings are editable; get_clip omits the field for any clip that is not.",
          "type": "string"
        },
        "target": {
          "description": "Which recorder to use when the workspace has both Code record and Browser record: 'code' for a mock of the product, 'browser' for the real product \u2014 from the user's request, or their answer when it didn't say. Omit otherwise.",
          "type": "string",
          "enum": [
            "code",
            "browser"
          ]
        },
        "edit_scene_ids": {
          "description": "The clip id(s) this edit replaces. Only used when recording_session_id is set; scenes you do not name are not re-filmed. Set preceding_clip_id to the clip you are replacing \u2014 an edit naming a clip that is not in the guide is refused rather than appended to the end.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "clueprint_id": {
          "description": "The clueprint this video is built from, if any (the id you passed to get_clueprint). Effects placed automatically on the recording follow that clueprint's effect rules; omit it and they follow the default rules.",
          "type": "string"
        }
      },
      "required": [
        "guide_id",
        "chat_id",
        "preceding_clip_id",
        "scenes"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "run_script": {
      "type": "object",
      "properties": {
        "code": {
          "type": "string",
          "minLength": 1,
          "maxLength": 200000,
          "description": "JavaScript. Tools are globals called synchronously \u2014 no async/await, no network or filesystem; `return` a value to hand it back."
        },
        "dry_run": {
          "description": "Read-only tools still run; anything that writes is recorded and skipped. Use it to check what a script would do before it does it.",
          "type": "boolean"
        },
        "wait_seconds": {
          "description": "How long to hold this call open waiting for the script, default 50s. If it finishes in time you get the result here; if not you get a job_id to poll with get_script_job, and the script keeps running either way.",
          "type": "integer",
          "minimum": 1,
          "maximum": 55
        },
        "idempotency_key": {
          "description": "Pass a unique string so a retry cannot run the same build twice. If a script with this key already ran in this workspace, its job is returned and nothing executes again \u2014 including when that run FAILED, because a script is not a transaction and the writes it made before failing are still there. To act after a failure, read the project, repair what landed, and use a NEW key. It is the only guard against a resend: without it, a resent call runs the whole script again. Worth setting for anything that builds.",
          "type": "string",
          "minLength": 8,
          "maxLength": 200
        },
        "timeout_seconds": {
          "description": "Wall-clock budget for the SCRIPT \u2014 separate from wait_seconds, which is only how long this call stays open. By default a script gets 50s while you are waiting on it, and is raised to 15 minutes the moment it outlives the wait and becomes a job you poll with get_script_job. Set this only to override BOTH with one fixed budget. Watch the slow tools: analyze_audio in 'transcript' or 'beats' mode, export_project and auto_sync take tens of seconds each and will eat a budget fast. Never wait on an export in a script: start it with export_project and end the script (get_export holds for its wait_seconds; pass 0 if you read one). analyze_audio in 'features' mode is ~1s and is fine to loop over.",
          "type": "integer",
          "minimum": 5,
          "maximum": 1800
        }
      },
      "required": [
        "code"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    },
    "get_script_job": {
      "type": "object",
      "properties": {
        "job_id": {
          "type": "string",
          "format": "uuid",
          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
          "description": "From the run_script reply."
        },
        "wait_seconds": {
          "description": "How long to wait for completion before answering, default 50s. Pass 0 for an immediate status check.",
          "type": "integer",
          "minimum": 0,
          "maximum": 55
        }
      },
      "required": [
        "job_id"
      ],
      "$schema": "https://json-schema.org/draft/2020-12/schema"
    }
  }
}