> ## Documentation Index
> Fetch the complete documentation index at: https://docs.framelane.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Get a project

> Returns a project. `view=summary` (default) omits the full request; `view=full` includes it.



## OpenAPI

````yaml /openapi.json get /v1/projects/{project_id}
openapi: 3.1.0
info:
  title: Framelane API
  description: >
    Framelane renders video and runs AI media tasks from a single declarative
    request.


    ### Getting access (self-serve, no human needed)

    You can provision your own workspace and API key end-to-end — no dashboard,
    no sales call:

    1. `POST /v1/signup` with a `workspace_name` and an `email` you control. You
    get an API key
       back immediately and a 6-digit code is emailed to that address.
    2. Read the code from that inbox and `POST /v1/signup/verify` with it. If
    you drive a
       programmatic inbox (e.g. an agent mail service), this whole loop runs unattended.
    3. Your key works the moment the email is verified. Until then every authed
    call returns
       `403 email_not_verified` — verification is the only gate, so an unverified workspace can
       never spend. Re-calling `POST /v1/signup` re-keys the workspace and requires re-verifying.

    `POST /v1/signup`, `POST /v1/signup/verify`, and `GET /v1/capabilities` need
    **no** API key.


    ### Authentication

    Send your API key as a bearer token: `Authorization: Bearer fl_live_...`.


    ### Core workflow

    1. **Get media in.** Pass any publicly accessible URL directly, or `POST
    /v1/uploads` to
       get a signed URL, then `PUT` your file to it.
    2. **Submit one JSON payload.** `POST /v1/renders` composes a whole scene
    (canvas +
       `elements[]` + `transitions[]`) in a single request; `POST /v1/tasks/{type}` runs an AI
       operation (transcribe).
    3. **Wait for completion.** Jobs are async: poll `GET
    /v1/{renders|tasks}/{id}` or register
       a webhook. Statuses end in `completed`, `failed`, or `cancelled`.
    4. **Fetch the result.** A render's artifact is a file: use `output.url` or
       `GET /v1/renders/{id}/download` for a short-lived signed URL. A task that returns data
       puts it on the job's `result` object — `transcribe` returns its transcript there, has no
       `output`, and `404`s on `/v1/tasks/{id}/download`.

    ### Discovering what's possible

    Call **`GET /v1/capabilities`** (no auth) for the machine-readable catalog
    of every effect,

    motion, transition, format, element type, and task parameter — each flagged
    with whether the

    renderer supports it, plus all numeric ranges and rate limits. Validate
    against it before

    submitting to avoid `422`s.


    ### Idempotency

    `POST` endpoints accept an `Idempotency-Key` header. The same key with the
    same body replays

    the original response (`200`); with a different body it returns `409
    Conflict`.


    ### Errors

    Every error has the shape `{"error": {"code", "message", "details"}}`. The
    machine-readable

    `code` (e.g. `source_not_found`, `quota_exceeded`, `codec_unsupported`) is
    stable — branch on

    it to self-correct.
  version: 0.2.0
servers:
  - url: https://api.framelane.io
    description: Production
security: []
tags:
  - name: capabilities
    description: Discover supported features, formats, and limits.
  - name: renders
    description: Compose and render video from a declarative timeline.
  - name: projects
    description: >-
      Stateful editing: read a composition, apply targeted ops, preview, and
      render the head.
  - name: preview
    description: >-
      Validate a composition for free and preview a frame or window cheaply,
      without a full render.
  - name: tasks
    description: 'Run AI media operations: transcription.'
  - name: uploads
    description: Get signed URLs to upload source media into Framelane storage.
  - name: webhooks
    description: Subscribe to job lifecycle events with signed delivery.
  - name: workspace
    description: Manage your workspace, usage, and assets.
  - name: api-keys
    description: Create and revoke API keys.
  - name: auth
    description: Session sync for the first-party console.
  - name: signup
    description: Create a workspace and verify email.
  - name: billing
    description: Manage your subscription and billing portal.
  - name: system
    description: Health, readiness, and version probes.
paths:
  /v1/projects/{project_id}:
    get:
      tags:
        - projects
      summary: Get a project
      description: >-
        Returns a project. `view=summary` (default) omits the full request;
        `view=full` includes it.
      operationId: get_project
      parameters:
        - name: project_id
          in: path
          required: true
          schema:
            type: string
            title: Project Id
        - name: view
          in: query
          required: false
          schema:
            $ref: '#/components/schemas/ProjectView'
            default: summary
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectOut'
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '404':
          description: Project not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
      security:
        - ApiKey: []
components:
  schemas:
    ProjectView:
      type: string
      enum:
        - summary
        - full
      title: ProjectView
      description: How much of a project to return from ``GET /v1/projects/{id}``.
    ProjectOut:
      properties:
        id:
          type: string
          title: Id
          description: Unique project id.
          example: proj_01J8QR2K5VKDGN2T4FBM3CZYX7
        name:
          type: string
          title: Name
          description: Display name.
          example: Launch teaser
        version:
          type: integer
          title: Version
          description: Head version. Bumped on every applied ops batch.
          example: 3
        workspace_id:
          type: string
          title: Workspace Id
          description: Owning workspace id.
        created_at:
          type: string
          format: date-time
          title: Created At
        updated_at:
          type: string
          format: date-time
          title: Updated At
        summary:
          $ref: '#/components/schemas/ProjectSummary'
          description: Compact, always-present view of the head composition.
        render_request:
          anyOf:
            - $ref: '#/components/schemas/RenderRequest'
            - type: 'null'
          description: >-
            The full head composition. Present only when requested with
            view=full.
      type: object
      required:
        - id
        - name
        - version
        - workspace_id
        - created_at
        - updated_at
        - summary
      title: ProjectOut
      description: A project. ``render_request`` is populated only for ``view=full``.
    ApiError:
      description: >-
        The error envelope returned on every 4xx/5xx response: `{"error":
        {...}}`.
      properties:
        error:
          $ref: '#/components/schemas/ErrorResponse'
      required:
        - error
      title: ApiError
      type: object
    ProjectSummary:
      properties:
        width:
          anyOf:
            - type: integer
            - type: 'null'
          title: Width
        height:
          anyOf:
            - type: integer
            - type: 'null'
          title: Height
        duration:
          anyOf:
            - type: number
            - type: 'null'
          title: Duration
        frame_rate:
          anyOf:
            - type: integer
            - type: 'null'
          title: Frame Rate
        output_format:
          anyOf:
            - type: string
            - type: 'null'
          title: Output Format
        element_count:
          type: integer
          title: Element Count
          default: 0
        transition_count:
          type: integer
          title: Transition Count
          default: 0
        renders_count:
          anyOf:
            - type: integer
            - type: 'null'
          title: Renders Count
          description: >-
            Number of billable renders submitted for this project. Null when not
            computed.
        elements:
          items:
            $ref: '#/components/schemas/ProjectElementSummary'
          type: array
          title: Elements
      type: object
      title: ProjectSummary
      description: 'A token-light view of a composition: canvas + one line per element.'
    RenderRequest:
      properties:
        width:
          anyOf:
            - type: integer
              maximum: 8192
              minimum: 16
            - type: 'null'
          title: Width
          description: >-
            Output width in pixels. Must be set together with `height` or both
            omitted.
          example: 1920
        height:
          anyOf:
            - type: integer
              maximum: 8192
              minimum: 16
            - type: 'null'
          title: Height
          description: >-
            Output height in pixels. Must be set together with `width` or both
            omitted.
          example: 1080
        duration:
          anyOf:
            - type: number
              exclusiveMinimum: 0
            - type: 'null'
          title: Duration
          description: >-
            Total composition duration in seconds. Inferred from elements when
            omitted.
          example: 15
        frame_rate:
          anyOf:
            - type: integer
              maximum: 240
              minimum: 1
            - type: 'null'
          title: Frame Rate
          description: >-
            Frames per second. When set, the output encodes at exactly this
            rate. Defaults to 30 when omitted.
          example: 30
        output_format:
          $ref: '#/components/schemas/OutputFormat'
          description: Container and codec for the output file.
          default: mp4
          example: mp4
        output_filename:
          anyOf:
            - type: string
            - type: 'null'
          title: Output Filename
          description: >-
            Custom filename for the artifact (without extension). Auto-generated
            when omitted.
          example: my-render
        background_color:
          type: string
          pattern: ^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$
          title: Background Color
          description: RGBA background color in hex format (#RRGGBBAA).
          default: '#000000ff'
          example: '#000000ff'
        background_image_url:
          anyOf:
            - type: string
              maxLength: 2083
              minLength: 1
              format: uri
            - type: 'null'
          title: Background Image Url
          description: URL of an image to use as the composition background.
          example: https://cdn.example.com/bg.jpg
        alpha:
          type: boolean
          title: Alpha
          description: >-
            When true, produce an alpha-channel (transparent background) output.
            Requires `output_format=webm` or `output_format=mov`.
          default: false
          example: false
        elements:
          items:
            oneOf:
              - $ref: '#/components/schemas/VideoElement'
              - $ref: '#/components/schemas/AudioElement'
              - $ref: '#/components/schemas/TextElement'
              - $ref: '#/components/schemas/ImageElement'
              - $ref: '#/components/schemas/ShapeElement'
            discriminator:
              propertyName: type
              mapping:
                audio:
                  $ref: '#/components/schemas/AudioElement'
                image:
                  $ref: '#/components/schemas/ImageElement'
                shape:
                  $ref: '#/components/schemas/ShapeElement'
                text:
                  $ref: '#/components/schemas/TextElement'
                video:
                  $ref: '#/components/schemas/VideoElement'
          type: array
          title: Elements
          description: Ordered list of timeline elements (video, image, text, audio, etc.).
        transitions:
          items:
            $ref: '#/components/schemas/Transition'
          type: array
          title: Transitions
          description: Transition effects applied between consecutive elements.
        groups:
          items:
            $ref: '#/components/schemas/Group'
          type: array
          title: Groups
          description: >-
            Transform groups over member elements (compose member→group→parent;
            optional stack layout). §5.
        motion_blur:
          anyOf:
            - $ref: '#/components/schemas/MotionBlur'
            - type: 'null'
          description: >-
            Shutter-based motion blur over the whole composition (§5c). Graphics
            motion smears; video holds its decoded frame within the shutter (as
            in AE). Opt-in and costly: accumulation is CPU-side, so the render
            falls back to software encoding — expect roughly `samples`× render
            time.
        background_gradient:
          anyOf:
            - type: string
              pattern: ^\s*(?:linear|radial)-gradient\s*\(
              description: >-
                CSS gradient shorthand — `linear-gradient(...)` or
                `radial-gradient(...)`. Normalised to the Gradient object during
                validation; the renderer never sees a CSS string, and reading
                the composition back returns the object.
              example: 'linear-gradient(90deg, #ff0000 0%, #0000ff 100%)'
            - $ref: '#/components/schemas/Gradient'
            - type: 'null'
          description: >-
            Canvas-sized gradient fill behind every layer (§6). Rendered for the
            full output slice. Accepts a Gradient object or a CSS
            `linear-gradient(...)` string.
        watermark_url:
          anyOf:
            - type: string
              maxLength: 2083
              minLength: 1
              format: uri
            - type: 'null'
          title: Watermark Url
          description: Watermark image overlaid on the output (§6).
        custom_animations:
          anyOf:
            - $ref: '#/components/schemas/CustomAnimationRegistry'
            - type: 'null'
          description: >-
            Inline custom-animation definitions (§3), registered with the engine
            before any element is built. Reference an `element` entry by name
            via `motion[].custom` on any video/image/shape element or group;
            reference a `text` entry via a text element's `animation_preset` or
            `motion[].custom`. The catalog stops being a ceiling — anything
            expressible as keyframes is expressible in the request.
        metadata:
          additionalProperties:
            type: string
          type: object
          title: Metadata
          description: Arbitrary key-value pairs echoed back in all webhook payloads.
          example:
            project_id: proj_123
        webhook_url:
          anyOf:
            - type: string
              maxLength: 2083
              minLength: 1
              format: uri
            - type: 'null'
          title: Webhook Url
          description: >-
            Per-request webhook URL. Overrides workspace-level webhooks for this
            job only. Receives `render.completed`, `render.failed`, and progress
            events.
          example: https://app.example.com/hooks/framelane
        ingest_external:
          anyOf:
            - type: boolean
            - type: 'null'
          title: Ingest External
          description: >-
            Controls handling of source URLs that are not already hosted by
            Framelane. When `true`, such public files are copied into Framelane
            storage before rendering: the job starts in the `ingesting` state
            and moves to `queued` once the copy completes (you receive an
            `asset.ready` webhook per file). When `false`, the URL is passed
            straight to the renderer (the legacy behavior; video/audio still
            require an explicit `out_point`). Defaults to the workspace setting
            when omitted.
          example: true
      additionalProperties: false
      type: object
      title: RenderRequest
      description: >-
        Body for ``POST /v1/renders``.


        Defines the composition to render: dimensions, output format, timeline
        elements,

        and optional transitions. The render engine produces a single video
        artifact.
    ErrorResponse:
      description: >-
        The structured error body — the value of the `error` key on an error
        response.
      properties:
        code:
          $ref: '#/components/schemas/ApiErrorCode'
        message:
          title: Message
          type: string
        details:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          default: null
          title: Details
      required:
        - code
        - message
      title: ErrorResponse
      type: object
    ProjectElementSummary:
      properties:
        id:
          anyOf:
            - type: string
            - type: 'null'
          title: Id
          description: Element id (the op target).
        type:
          type: string
          title: Type
          description: 'Element type: video, audio, text, image, etc.'
        time:
          anyOf:
            - type: number
            - type: 'null'
          title: Time
          description: Start time on the timeline, in seconds.
        duration:
          anyOf:
            - type: number
            - type: 'null'
          title: Duration
          description: On-timeline duration, when derivable.
        label:
          anyOf:
            - type: string
            - type: 'null'
          title: Label
          description: Human label (name / text), truncated.
        source:
          anyOf:
            - type: string
            - type: 'null'
          title: Source
          description: Source filename for media elements.
      type: object
      required:
        - type
      title: ProjectElementSummary
      description: >-
        One compact line per timeline element, enough for an agent to address
        it.
    OutputFormat:
      type: string
      enum:
        - mp4
        - webm
        - mov
        - gif
        - png
        - jpg
      title: OutputFormat
      description: >-
        Values:

        - `mp4` — H.264 MP4 video — the default, widely compatible delivery
        format.

        - `webm` — VP9 WebM video — supports a transparent (alpha) background.

        - `mov` — QuickTime MOV — ProRes 4444 with a transparent (alpha)
        background; large files.

        - `gif` — Animated GIF — roadmap; not yet emitted by the renderer.

        - `png` — Single-frame PNG still — roadmap; not yet emitted by the
        renderer.

        - `jpg` — Single-frame JPEG still — roadmap; not yet emitted by the
        renderer.
      x-enumDescriptions:
        mp4: H.264 MP4 video — the default, widely compatible delivery format.
        webm: VP9 WebM video — supports a transparent (alpha) background.
        mov: >-
          QuickTime MOV — ProRes 4444 with a transparent (alpha) background;
          large files.
        gif: Animated GIF — roadmap; not yet emitted by the renderer.
        png: Single-frame PNG still — roadmap; not yet emitted by the renderer.
        jpg: Single-frame JPEG still — roadmap; not yet emitted by the renderer.
    VideoElement:
      properties:
        lut_url:
          anyOf:
            - type: string
              maxLength: 2083
              minLength: 1
              format: uri
            - type: 'null'
          title: Lut Url
        lut_intensity:
          type: number
          maximum: 100
          minimum: 0
          title: Lut Intensity
          default: 100
        brightness:
          type: number
          title: Brightness
          description: >-
            Brightness, -100..100, applied as a 0..2 colour multiplier (0 = no
            change). `-100` multiplies by 0: the element renders pure black
            while keeping its alpha — a silhouette of the artwork. Combine a
            `brightness: -100` copy of an image with `blur`, reduced `opacity`
            and a small offset, stacked beneath the real one, to fake the
            content-shaped drop shadow `shadow_x`/`shadow_y` cannot produce.
          default: 0
        contrast:
          type: number
          title: Contrast
          default: 0
        saturation:
          type: number
          title: Saturation
          default: 0
        exposure:
          type: number
          title: Exposure
          default: 0
        sharpness:
          type: number
          title: Sharpness
          default: 0
        blur:
          type: number
          title: Blur
          default: 0
        noise:
          type: number
          title: Noise
          default: 0
        vignette:
          type: number
          title: Vignette
          default: 0
        hue_rotate:
          type: number
          title: Hue Rotate
          default: 0
        temperature:
          type: number
          title: Temperature
          description: >-
            White balance warm (+) / cool (−), −1..1; 0 = no-op. Applied as a
            channel matrix, after the classic brightness/contrast chain.
          default: 0
        tint:
          type: number
          title: Tint
          description: White balance magenta (+) / green (−), −1..1; 0 = no-op.
          default: 0
        vibrance:
          type: number
          title: Vibrance
          description: >-
            Saturation boost weighted toward muted colours, −1..1; 0 = no-op.
            Boosts flat colour without wrecking skin tones.
          default: 0
        highlights:
          type: number
          title: Highlights
          description: Luma-masked tone lift of the brightest range, −1..1; 0 = no-op.
          default: 0
        shadows:
          type: number
          title: Shadows
          description: Luma-masked tone lift of the darkest range, −1..1; 0 = no-op.
          default: 0
        crop_top:
          type: number
          maximum: 1
          minimum: 0
          title: Crop Top
          default: 0
        crop_bottom:
          type: number
          maximum: 1
          minimum: 0
          title: Crop Bottom
          default: 0
        crop_left:
          type: number
          maximum: 1
          minimum: 0
          title: Crop Left
          default: 0
        crop_right:
          type: number
          maximum: 1
          minimum: 0
          title: Crop Right
          default: 0
        border_radius:
          anyOf:
            - type: integer
            - type: number
            - type: string
            - $ref: '#/components/schemas/CornerRadiiPx'
          title: Border Radius
          description: >-
            Corner radius in pixels — a single value, or per-corner `{top_left,
            top_right, bottom_right, bottom_left}` for asymmetric corners (e.g.
            a card rounded only on top). The renderer normalizes px to a 0–1
            fraction of the element's shorter side (≈ half the shorter side is
            fully rounded) and clamps to that range. For shape elements use
            `corner_radius` (already 0–1).
          default: 0
        border_color:
          anyOf:
            - type: string
              pattern: ^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$
            - type: 'null'
          title: Border Color
        border_width:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Border Width
          description: >-
            Border thickness in pixels (converted to the renderer's relative
            border scale against the element's shorter side, so it survives
            resizes). The border outlines the element's border_radius-rounded
            quad — not the image's alpha silhouette.
          default: 0
        shadow_color:
          anyOf:
            - type: string
              pattern: ^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$
            - type: 'null'
          title: Shadow Color
        shadow_blur:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Shadow Blur
          description: >-
            Shadow softness, 0–100 (renderer blur-strength scale, not pixels): 0
            = hard edge, 100 = maximum blur. Values are clamped to that range.
          default: 0
        shadow_x:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Shadow X
          description: >-
            Horizontal shadow offset in pixels (converted to the renderer's
            relative offset scale). The shadow silhouette is the element's
            border_radius-rounded quad, not the content alpha — a cut-out PNG
            gets a card shadow, not an outline-shaped one. On a cut-out that
            reads as a floating rectangle (rotated with the element, which is
            the tell). For a shadow that follows the artwork, drop these fields
            and stack a second copy of the same image underneath at `brightness:
            -100` (a black silhouette, alpha preserved) with `blur` and reduced
            `opacity`, offset a few percent — see `brightness`.
          default: 0
        shadow_y:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Shadow Y
          description: Vertical shadow offset in pixels. See `shadow_x`.
          default: 0
        blend_mode:
          $ref: '#/components/schemas/BlendMode'
          default: none
        mask_shape:
          anyOf:
            - $ref: '#/components/schemas/MaskShape'
            - type: 'null'
        mask:
          anyOf:
            - $ref: '#/components/schemas/MaskConfig'
            - type: 'null'
        mask_keyframes:
          anyOf:
            - items:
                $ref: '#/components/schemas/MaskKeyframe'
              type: array
            - type: 'null'
          title: Mask Keyframes
        matte:
          anyOf:
            - $ref: '#/components/schemas/Matte'
            - type: 'null'
        backdrop_blur:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Backdrop Blur
          default: 0
        x:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: X
          default: 50%
        'y':
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: 'Y'
          default: 50%
        width:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Width
          description: >-
            Box width. Together with `height` this is a **cover** box, not a fit
            box: sized media (video, image) scales to fill it and the overflow
            is **cropped** — it is never letterboxed. A box whose aspect ratio
            differs from the source's therefore silently cuts the source's
            edges. To show a source whole, match the box aspect to it: on a
            `W`x`H` canvas, for a source of aspect `a`, `width% = a * height% *
            (H/W)` — e.g. a 1024x1536 asset (a=0.667) on 1920x1080 needs `width%
            = 0.375 * height%`. Percentages are of the canvas, not the parent.
          default: 100%
          example: 40%
        height:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Height
          description: >-
            Box height. See `width` — the pair is a cover box and crops rather
            than letterboxes. Percentages are of the canvas.
          default: 100%
          example: 80%
        aspect_ratio:
          anyOf:
            - type: number
            - type: 'null'
          title: Aspect Ratio
          description: >-
            **Accepted but ignored — the renderer never receives this field.**
            It does NOT constrain or correct the `width`/`height` box, so it
            cannot be used to stop a mismatched box from cropping the source;
            size the box per `width` instead. Kept for backward compatibility.
        x_anchor:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: X Anchor
          description: >-
            Horizontal pivot the element **rotates** about, measured across its
            own box (`0%` = left edge, `50%` = centre, `100%` = right edge; px
            values are an offset from the left edge). This is a pivot, not an
            alignment — `x` always places the element's centre, so the anchor
            changes nothing on an unrotated element. Honored on video, image and
            shape; text and the generator elements rotate about their centre and
            reject a non-default anchor.
          default: 50%
          example: 0%
        y_anchor:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Y Anchor
          description: >-
            Vertical pivot the element **rotates** about, measured across its
            own box (`0%` = top edge, `50%` = centre, `100%` = bottom edge).
            Same rules as `x_anchor`: a pivot, not an alignment.
          default: 50%
          example: 100%
        x_rotation:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: X Rotation
          default: 0°
        y_rotation:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Y Rotation
          default: 0°
        z_rotation:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Z Rotation
          default: 0°
        x_scale:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: X Scale
          default: 100%
        y_scale:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Y Scale
          default: 100%
        flip_horizontal:
          type: boolean
          title: Flip Horizontal
          default: false
        flip_vertical:
          type: boolean
          title: Flip Vertical
          default: false
        opacity:
          type: number
          maximum: 100
          minimum: 0
          title: Opacity
          default: 100
        z_index:
          anyOf:
            - type: integer
            - type: 'null'
          title: Z Index
          description: >-
            Stacking order (higher = on top). When omitted, elements stack in
            array order — later elements render above earlier ones (painter's
            rule); text defaults one layer above non-text. Set explicit values
            only when array order isn't the order you want.
        clip:
          type: boolean
          title: Clip
          default: false
        color_overlay:
          anyOf:
            - type: string
              pattern: ^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$
            - type: 'null'
          title: Color Overlay
          description: >-
            **Not supported by the renderer — setting this is rejected.** A
            strict submit returns `unsupported_feature` (422) and a dry-run
            reports it as a blocking violation; there is no native tint field to
            map it onto. To darken an element use `brightness` (`-100` is a
            black silhouette); to lay a colour over one, stack a `shape` with a
            flat or gradient `fill` above it.
        type:
          type: string
          const: video
          title: Type
          default: video
        id:
          anyOf:
            - type: string
            - type: 'null'
          title: Id
          description: >-
            Unique identifier for this element. **Required on image elements** —
            an empty or missing ID causes the image to be silently skipped by
            the renderer. Recommended on all elements that are referenced by a
            transition.
          example: clip_01
        name:
          anyOf:
            - type: string
            - type: 'null'
          title: Name
          description: Optional human-readable label. Not used by the renderer.
        track:
          anyOf:
            - type: integer
              maximum: 255
              minimum: 0
            - type: 'null'
          title: Track
          description: >-
            Timeline track index (0–255). Informational only; not used by the
            renderer.
        time:
          anyOf:
            - type: number
              minimum: 0
            - type: 'null'
          title: Time
          description: >-
            When this element starts on the output timeline, in seconds.
            Defaults to `0` when omitted.
          example: 0
        visible:
          type: boolean
          title: Visible
          description: >-
            Set to `false` to skip this element without removing it from the
            request.
          default: true
        source_url:
          type: string
          maxLength: 2083
          minLength: 1
          format: uri
          title: Source Url
          description: >-
            URL of the video file — MP4, MOV or WebM only; any other extension
            is rejected at submit. Must be accessible by the renderer.
          example: https://cdn.example.com/clip.mp4
        in_point:
          anyOf:
            - type: number
              minimum: 0
            - type: 'null'
          title: In Point
          description: >-
            In point — seconds into the source file to start playing from.
            Defaults to `0`.
          example: 0
        out_point:
          anyOf:
            - type: number
              exclusiveMinimum: 0
            - type: 'null'
          title: Out Point
          description: >-
            Out point — seconds into the source file to stop playing. Defaults
            to end of source when omitted. Controls clip length on the timeline.
          example: 6
        speed:
          type: number
          maximum: 4
          minimum: 0.25
          title: Speed
          description: >-
            Playback speed. `1.0` = normal speed, `2.0` = 2× (half duration),
            `0.5` = half speed (double duration).
          default: 1
          example: 1
        playback_rate_keyframes:
          anyOf:
            - items:
                $ref: '#/components/schemas/RateKeyframe'
              type: array
              maxItems: 512
            - type: 'null'
          title: Playback Rate Keyframes
          description: >-
            Speed ramp (§5c): piecewise-linear playback-rate keyframes (absolute
            timeline seconds; rate 0 = freeze; at most 512 keyframes — the
            renderer truncates beyond that). Audio keeps the static rate — mute
            or detach it on ramped clips.
        volume:
          type: number
          maximum: 100
          minimum: 0
          title: Volume
          description: >-
            Embedded audio volume as a percentage (0–100). Affects only the
            audio track inside this video clip.
          default: 100
          example: 100
        fade_in_duration:
          type: number
          title: Fade In Duration
          description: Duration in seconds of a linear audio fade-in at the clip's start.
          default: 0
          example: 0
        fade_out_duration:
          type: number
          title: Fade Out Duration
          description: Duration in seconds of a linear audio fade-out at the clip's end.
          default: 0
          example: 0
        effects:
          items:
            $ref: '#/components/schemas/Effect'
          type: array
          title: Effects
          default: []
        motion:
          items:
            $ref: '#/components/schemas/Motion'
          type: array
          title: Motion
          default: []
      additionalProperties: false
      type: object
      required:
        - source_url
      title: VideoElement
    AudioElement:
      properties:
        type:
          type: string
          const: audio
          title: Type
          default: audio
        id:
          anyOf:
            - type: string
            - type: 'null'
          title: Id
          description: >-
            Unique identifier for this element. **Required on image elements** —
            an empty or missing ID causes the image to be silently skipped by
            the renderer. Recommended on all elements that are referenced by a
            transition.
          example: clip_01
        name:
          anyOf:
            - type: string
            - type: 'null'
          title: Name
          description: Optional human-readable label. Not used by the renderer.
        track:
          anyOf:
            - type: integer
              maximum: 255
              minimum: 0
            - type: 'null'
          title: Track
          description: >-
            Timeline track index (0–255). Informational only; not used by the
            renderer.
        time:
          anyOf:
            - type: number
              minimum: 0
            - type: 'null'
          title: Time
          description: >-
            When this element starts on the output timeline, in seconds.
            Defaults to `0` when omitted.
          example: 0
        visible:
          type: boolean
          title: Visible
          description: >-
            Set to `false` to skip this element. Audio has no picture — `false`
            drops the whole track from the render, it does not mute or hide it
            (the linter reports that as `AUDIO_HIDDEN`). There is no
            keep-but-silence value: `volume` must be > 0.
          default: true
        source_url:
          type: string
          maxLength: 2083
          minLength: 1
          format: uri
          title: Source Url
          description: URL of the audio file. Must be accessible by the renderer.
          example: https://cdn.example.com/music.mp3
        in_point:
          anyOf:
            - type: number
              minimum: 0
            - type: 'null'
          title: In Point
          description: >-
            In point — seconds into the source file to start from. Defaults to
            `0`.
          example: 0
        out_point:
          anyOf:
            - type: number
              exclusiveMinimum: 0
            - type: 'null'
          title: Out Point
          description: >-
            Out point — seconds into the source file to stop. Defaults to end of
            source when omitted. Controls clip length on the timeline.
          example: 30
        speed:
          type: number
          maximum: 4
          minimum: 0.25
          title: Speed
          description: Playback speed. `1.0` = normal speed, `2.0` = 2× speed.
          default: 1
          example: 1
        volume:
          type: number
          maximum: 100
          exclusiveMinimum: 0
          title: Volume
          description: >-
            Audio volume as a percentage (1–100). **Must be greater than 0** —
            the renderer silently drops audio streams with `volume <= 0`.
          default: 100
          example: 100
        fade_in_duration:
          type: number
          title: Fade In Duration
          description: Duration in seconds of a linear audio fade-in at the clip's start.
          default: 0
          example: 0
        fade_out_duration:
          type: number
          title: Fade Out Duration
          description: Duration in seconds of a linear audio fade-out at the clip's end.
          default: 0
          example: 0
      additionalProperties: false
      type: object
      required:
        - source_url
      title: AudioElement
    TextElement:
      properties:
        border_radius:
          anyOf:
            - type: integer
            - type: number
            - type: string
            - $ref: '#/components/schemas/CornerRadiiPx'
          title: Border Radius
          description: >-
            Corner radius in pixels — a single value, or per-corner `{top_left,
            top_right, bottom_right, bottom_left}` for asymmetric corners (e.g.
            a card rounded only on top). The renderer normalizes px to a 0–1
            fraction of the element's shorter side (≈ half the shorter side is
            fully rounded) and clamps to that range. For shape elements use
            `corner_radius` (already 0–1).
          default: 0
        border_color:
          anyOf:
            - type: string
              pattern: ^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$
            - type: 'null'
          title: Border Color
        border_width:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Border Width
          description: >-
            Border thickness in pixels (converted to the renderer's relative
            border scale against the element's shorter side, so it survives
            resizes). The border outlines the element's border_radius-rounded
            quad — not the image's alpha silhouette.
          default: 0
        shadow_color:
          anyOf:
            - type: string
              pattern: ^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$
            - type: 'null'
          title: Shadow Color
        shadow_blur:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Shadow Blur
          description: >-
            Shadow softness, 0–100 (renderer blur-strength scale, not pixels): 0
            = hard edge, 100 = maximum blur. Values are clamped to that range.
          default: 0
        shadow_x:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Shadow X
          description: >-
            Horizontal shadow offset in pixels (converted to the renderer's
            relative offset scale). The shadow silhouette is the element's
            border_radius-rounded quad, not the content alpha — a cut-out PNG
            gets a card shadow, not an outline-shaped one. On a cut-out that
            reads as a floating rectangle (rotated with the element, which is
            the tell). For a shadow that follows the artwork, drop these fields
            and stack a second copy of the same image underneath at `brightness:
            -100` (a black silhouette, alpha preserved) with `blur` and reduced
            `opacity`, offset a few percent — see `brightness`.
          default: 0
        shadow_y:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Shadow Y
          description: Vertical shadow offset in pixels. See `shadow_x`.
          default: 0
        x:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: X
          default: 50%
        'y':
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: 'Y'
          default: 50%
        width:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Width
          description: >-
            Box width. Together with `height` this is a **cover** box, not a fit
            box: sized media (video, image) scales to fill it and the overflow
            is **cropped** — it is never letterboxed. A box whose aspect ratio
            differs from the source's therefore silently cuts the source's
            edges. To show a source whole, match the box aspect to it: on a
            `W`x`H` canvas, for a source of aspect `a`, `width% = a * height% *
            (H/W)` — e.g. a 1024x1536 asset (a=0.667) on 1920x1080 needs `width%
            = 0.375 * height%`. Percentages are of the canvas, not the parent.
          default: 100%
          example: 40%
        height:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Height
          description: >-
            Box height. See `width` — the pair is a cover box and crops rather
            than letterboxes. Percentages are of the canvas.
          default: 100%
          example: 80%
        aspect_ratio:
          anyOf:
            - type: number
            - type: 'null'
          title: Aspect Ratio
          description: >-
            **Accepted but ignored — the renderer never receives this field.**
            It does NOT constrain or correct the `width`/`height` box, so it
            cannot be used to stop a mismatched box from cropping the source;
            size the box per `width` instead. Kept for backward compatibility.
        x_anchor:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: X Anchor
          description: >-
            Horizontal pivot the element **rotates** about, measured across its
            own box (`0%` = left edge, `50%` = centre, `100%` = right edge; px
            values are an offset from the left edge). This is a pivot, not an
            alignment — `x` always places the element's centre, so the anchor
            changes nothing on an unrotated element. Honored on video, image and
            shape; text and the generator elements rotate about their centre and
            reject a non-default anchor.
          default: 50%
          example: 0%
        y_anchor:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Y Anchor
          description: >-
            Vertical pivot the element **rotates** about, measured across its
            own box (`0%` = top edge, `50%` = centre, `100%` = bottom edge).
            Same rules as `x_anchor`: a pivot, not an alignment.
          default: 50%
          example: 100%
        x_rotation:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: X Rotation
          default: 0°
        y_rotation:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Y Rotation
          default: 0°
        z_rotation:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Z Rotation
          default: 0°
        x_scale:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: X Scale
          default: 100%
        y_scale:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Y Scale
          default: 100%
        flip_horizontal:
          type: boolean
          title: Flip Horizontal
          default: false
        flip_vertical:
          type: boolean
          title: Flip Vertical
          default: false
        opacity:
          type: number
          maximum: 100
          minimum: 0
          title: Opacity
          default: 100
        z_index:
          anyOf:
            - type: integer
            - type: 'null'
          title: Z Index
          description: >-
            Stacking order (higher = on top). When omitted, elements stack in
            array order — later elements render above earlier ones (painter's
            rule); text defaults one layer above non-text. Set explicit values
            only when array order isn't the order you want.
        clip:
          type: boolean
          title: Clip
          default: false
        color_overlay:
          anyOf:
            - type: string
              pattern: ^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$
            - type: 'null'
          title: Color Overlay
          description: >-
            **Not supported by the renderer — setting this is rejected.** A
            strict submit returns `unsupported_feature` (422) and a dry-run
            reports it as a blocking violation; there is no native tint field to
            map it onto. To darken an element use `brightness` (`-100` is a
            black silhouette); to lay a colour over one, stack a `shape` with a
            flat or gradient `fill` above it.
        type:
          type: string
          const: text
          title: Type
          default: text
        id:
          anyOf:
            - type: string
            - type: 'null'
          title: Id
          description: >-
            Unique identifier for this element. **Required on image elements** —
            an empty or missing ID causes the image to be silently skipped by
            the renderer. Recommended on all elements that are referenced by a
            transition.
          example: clip_01
        name:
          anyOf:
            - type: string
            - type: 'null'
          title: Name
          description: Optional human-readable label. Not used by the renderer.
        track:
          anyOf:
            - type: integer
              maximum: 255
              minimum: 0
            - type: 'null'
          title: Track
          description: >-
            Timeline track index (0–255). Informational only; not used by the
            renderer.
        time:
          anyOf:
            - type: number
              minimum: 0
            - type: 'null'
          title: Time
          description: >-
            When this element starts on the output timeline, in seconds.
            Defaults to `0` when omitted.
          example: 0
        visible:
          type: boolean
          title: Visible
          description: >-
            Set to `false` to skip this element without removing it from the
            request.
          default: true
        duration:
          type: number
          exclusiveMinimum: 0
          title: Duration
          description: >-
            **Required.** Duration in seconds the text is visible. Omitting this
            field produces a zero-length window and the element will not appear
            in the render.
          example: 5
        text:
          type: string
          minLength: 1
          title: Text
          description: The text content to render.
          example: Hello World
        font_family:
          type: string
          title: Font Family
          description: Font family name. Must be available to the renderer.
          default: Inter
          example: Inter
        font_size:
          type: number
          exclusiveMinimum: 0
          title: Font Size
          description: Font size in pixels.
          default: 16
          example: 48
        font_weight:
          type: integer
          maximum: 900
          minimum: 100
          title: Font Weight
          description: >-
            Font weight (100–900). The renderer has no variable weights: `600`
            and above render **bold**, everything below renders regular.
            Composes with `font_style` (italic + ≥600 = bold italic).
          default: 400
          example: 400
        font_style:
          type: string
          enum:
            - normal
            - italic
            - bold
            - bolditalic
          title: Font Style
          description: Font style variant.
          default: normal
        text_color:
          type: string
          pattern: ^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$
          title: Text Color
          description: Text fill color as a hex string.
          default: '#ffffff'
          example: '#ffffff'
        text_align:
          type: string
          enum:
            - left
            - center
            - right
          title: Text Align
          description: Horizontal text alignment.
          default: center
        text_direction:
          type: string
          enum:
            - ltr
            - rtl
          title: Text Direction
          description: Text direction. `rtl` for right-to-left scripts (§4).
          default: ltr
        text_decoration:
          type: string
          enum:
            - none
            - underline
            - strikethrough
          title: Text Decoration
          description: >-
            Text decoration. Only `none` renders. `underline` is reachable only
            per word — `words[].style.underline` — and `strikethrough` has no
            renderer path at all; both are rejected here (422) rather than
            silently dropped. Draw a strike-through as a thin `shape` rectangle
            over the text.
          default: none
        tracking:
          type: number
          title: Tracking
          description: >-
            Letter spacing (tracking) in pixels (converted to the renderer's
            width-relative unit on the wire). Positive values add space between
            characters.
          default: 0
          example: 0
        leading:
          type: number
          title: Leading
          description: >-
            Line height (leading) as a multiplier of font size. `1.2` = 20%
            taller than the font size.
          default: 1.2
          example: 1.2
        stroke_color:
          anyOf:
            - type: string
              pattern: ^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$
            - type: 'null'
          title: Stroke Color
          description: >-
            Outline color. Set together with `stroke_width` to enable text
            outlines.
          example: '#000000'
        stroke_width:
          type: number
          title: Stroke Width
          description: >-
            Outline width, em-relative (fraction of `font_size` — e.g. `0.05` =
            5% of the font size). Has no effect unless `stroke_color` is also
            set.
          default: 0
        background_color:
          anyOf:
            - type: string
              pattern: ^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$
            - type: 'null'
          title: Background Color
          description: >-
            Secondary colour. Fills the box behind the text when `background` is
            `true`, **and** is the highlight colour for `word_animation.style`
            `color` (the active word) and `box` (the box) — those two styles
            require it.
        background_opacity:
          type: number
          maximum: 100
          minimum: 0
          title: Background Opacity
          default: 100
        x_padding:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: X Padding
          default: 0
        y_padding:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Y Padding
          default: 0
        background:
          type: boolean
          title: Background
          description: >-
            Solid color box behind the full text block. Set `background_color`
            to choose the color.
          default: false
        stroke:
          type: boolean
          title: Stroke
          description: >-
            Outline-only mode — draws the glyph stroke with no fill. Requires
            `stroke_color` and `stroke_width`. When `false`, setting
            `stroke_color` + `stroke_width` gives fill + stroke (outlined).
          default: false
        shadow:
          type: boolean
          title: Shadow
          description: Built-in drop shadow.
          default: false
        motion:
          items:
            $ref: '#/components/schemas/Motion'
          type: array
          title: Motion
          default: []
        word_animation:
          anyOf:
            - $ref: '#/components/schemas/WordAnimation'
            - type: 'null'
          description: >-
            Word-level animation style with per-word timestamps. When provided,
            this takes precedence over the `animation_preset` field.
          example:
            style: glow
            words:
              - end: 0.5
                start: 0
                text: Hello
        words:
          anyOf:
            - items:
                $ref: '#/components/schemas/WordSpec'
              type: array
            - type: 'null'
          title: Words
          description: >-
            Per-word timing and optional per-word style overrides (§4). Emitted
            as a top-level `words[]` array; use alongside
            `motion`/`animation_preset` for the animation. Editorial-emphasis
            captions = word timings + a styled keyword.
          example:
            - end: 3
              start: 0
              style:
                color: '#e8734a'
              word: launch
        counter:
          anyOf:
            - $ref: '#/components/schemas/ValueCounter'
            - type: 'null'
          description: >-
            Rewrite this text from an eased numeric sweep (§4). Takes precedence
            over countdown on the same element.
        glow:
          anyOf:
            - $ref: '#/components/schemas/TextGlow'
            - type: 'null'
          description: Soft glow around the glyphs (§4).
        text_wrap:
          type: string
          enum:
            - wrap
            - nowrap
          title: Text Wrap
          default: wrap
        animation_preset:
          anyOf:
            - type: string
            - type: 'null'
          title: Animation Preset
          description: >-
            Renderer text-animation name (e.g. `'typewriter'`) — a catalog text
            animation or a `custom_animations.text` entry. Runs for the whole
            element window with per-glyph timings computed by the renderer. Use
            `motion[]` (with `type` or `custom`) for structured timing control —
            but not both. Ignored when `word_animation` is set.
      additionalProperties: false
      type: object
      required:
        - duration
        - text
      title: TextElement
    ImageElement:
      properties:
        lut_url:
          anyOf:
            - type: string
              maxLength: 2083
              minLength: 1
              format: uri
            - type: 'null'
          title: Lut Url
        lut_intensity:
          type: number
          maximum: 100
          minimum: 0
          title: Lut Intensity
          default: 100
        brightness:
          type: number
          title: Brightness
          description: >-
            Brightness, -100..100, applied as a 0..2 colour multiplier (0 = no
            change). `-100` multiplies by 0: the element renders pure black
            while keeping its alpha — a silhouette of the artwork. Combine a
            `brightness: -100` copy of an image with `blur`, reduced `opacity`
            and a small offset, stacked beneath the real one, to fake the
            content-shaped drop shadow `shadow_x`/`shadow_y` cannot produce.
          default: 0
        contrast:
          type: number
          title: Contrast
          default: 0
        saturation:
          type: number
          title: Saturation
          default: 0
        exposure:
          type: number
          title: Exposure
          default: 0
        sharpness:
          type: number
          title: Sharpness
          default: 0
        blur:
          type: number
          title: Blur
          default: 0
        noise:
          type: number
          title: Noise
          default: 0
        vignette:
          type: number
          title: Vignette
          default: 0
        hue_rotate:
          type: number
          title: Hue Rotate
          default: 0
        temperature:
          type: number
          title: Temperature
          description: >-
            White balance warm (+) / cool (−), −1..1; 0 = no-op. Applied as a
            channel matrix, after the classic brightness/contrast chain.
          default: 0
        tint:
          type: number
          title: Tint
          description: White balance magenta (+) / green (−), −1..1; 0 = no-op.
          default: 0
        vibrance:
          type: number
          title: Vibrance
          description: >-
            Saturation boost weighted toward muted colours, −1..1; 0 = no-op.
            Boosts flat colour without wrecking skin tones.
          default: 0
        highlights:
          type: number
          title: Highlights
          description: Luma-masked tone lift of the brightest range, −1..1; 0 = no-op.
          default: 0
        shadows:
          type: number
          title: Shadows
          description: Luma-masked tone lift of the darkest range, −1..1; 0 = no-op.
          default: 0
        crop_top:
          type: number
          maximum: 1
          minimum: 0
          title: Crop Top
          default: 0
        crop_bottom:
          type: number
          maximum: 1
          minimum: 0
          title: Crop Bottom
          default: 0
        crop_left:
          type: number
          maximum: 1
          minimum: 0
          title: Crop Left
          default: 0
        crop_right:
          type: number
          maximum: 1
          minimum: 0
          title: Crop Right
          default: 0
        border_radius:
          anyOf:
            - type: integer
            - type: number
            - type: string
            - $ref: '#/components/schemas/CornerRadiiPx'
          title: Border Radius
          description: >-
            Corner radius in pixels — a single value, or per-corner `{top_left,
            top_right, bottom_right, bottom_left}` for asymmetric corners (e.g.
            a card rounded only on top). The renderer normalizes px to a 0–1
            fraction of the element's shorter side (≈ half the shorter side is
            fully rounded) and clamps to that range. For shape elements use
            `corner_radius` (already 0–1).
          default: 0
        border_color:
          anyOf:
            - type: string
              pattern: ^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$
            - type: 'null'
          title: Border Color
        border_width:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Border Width
          description: >-
            Border thickness in pixels (converted to the renderer's relative
            border scale against the element's shorter side, so it survives
            resizes). The border outlines the element's border_radius-rounded
            quad — not the image's alpha silhouette.
          default: 0
        shadow_color:
          anyOf:
            - type: string
              pattern: ^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$
            - type: 'null'
          title: Shadow Color
        shadow_blur:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Shadow Blur
          description: >-
            Shadow softness, 0–100 (renderer blur-strength scale, not pixels): 0
            = hard edge, 100 = maximum blur. Values are clamped to that range.
          default: 0
        shadow_x:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Shadow X
          description: >-
            Horizontal shadow offset in pixels (converted to the renderer's
            relative offset scale). The shadow silhouette is the element's
            border_radius-rounded quad, not the content alpha — a cut-out PNG
            gets a card shadow, not an outline-shaped one. On a cut-out that
            reads as a floating rectangle (rotated with the element, which is
            the tell). For a shadow that follows the artwork, drop these fields
            and stack a second copy of the same image underneath at `brightness:
            -100` (a black silhouette, alpha preserved) with `blur` and reduced
            `opacity`, offset a few percent — see `brightness`.
          default: 0
        shadow_y:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Shadow Y
          description: Vertical shadow offset in pixels. See `shadow_x`.
          default: 0
        blend_mode:
          $ref: '#/components/schemas/BlendMode'
          default: none
        mask_shape:
          anyOf:
            - $ref: '#/components/schemas/MaskShape'
            - type: 'null'
        mask:
          anyOf:
            - $ref: '#/components/schemas/MaskConfig'
            - type: 'null'
        mask_keyframes:
          anyOf:
            - items:
                $ref: '#/components/schemas/MaskKeyframe'
              type: array
            - type: 'null'
          title: Mask Keyframes
        matte:
          anyOf:
            - $ref: '#/components/schemas/Matte'
            - type: 'null'
        backdrop_blur:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Backdrop Blur
          default: 0
        x:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: X
          default: 50%
        'y':
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: 'Y'
          default: 50%
        width:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Width
          description: >-
            Box width. Together with `height` this is a **cover** box, not a fit
            box: sized media (video, image) scales to fill it and the overflow
            is **cropped** — it is never letterboxed. A box whose aspect ratio
            differs from the source's therefore silently cuts the source's
            edges. To show a source whole, match the box aspect to it: on a
            `W`x`H` canvas, for a source of aspect `a`, `width% = a * height% *
            (H/W)` — e.g. a 1024x1536 asset (a=0.667) on 1920x1080 needs `width%
            = 0.375 * height%`. Percentages are of the canvas, not the parent.
          default: 100%
          example: 40%
        height:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Height
          description: >-
            Box height. See `width` — the pair is a cover box and crops rather
            than letterboxes. Percentages are of the canvas.
          default: 100%
          example: 80%
        aspect_ratio:
          anyOf:
            - type: number
            - type: 'null'
          title: Aspect Ratio
          description: >-
            **Accepted but ignored — the renderer never receives this field.**
            It does NOT constrain or correct the `width`/`height` box, so it
            cannot be used to stop a mismatched box from cropping the source;
            size the box per `width` instead. Kept for backward compatibility.
        x_anchor:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: X Anchor
          description: >-
            Horizontal pivot the element **rotates** about, measured across its
            own box (`0%` = left edge, `50%` = centre, `100%` = right edge; px
            values are an offset from the left edge). This is a pivot, not an
            alignment — `x` always places the element's centre, so the anchor
            changes nothing on an unrotated element. Honored on video, image and
            shape; text and the generator elements rotate about their centre and
            reject a non-default anchor.
          default: 50%
          example: 0%
        y_anchor:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Y Anchor
          description: >-
            Vertical pivot the element **rotates** about, measured across its
            own box (`0%` = top edge, `50%` = centre, `100%` = bottom edge).
            Same rules as `x_anchor`: a pivot, not an alignment.
          default: 50%
          example: 100%
        x_rotation:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: X Rotation
          default: 0°
        y_rotation:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Y Rotation
          default: 0°
        z_rotation:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Z Rotation
          default: 0°
        x_scale:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: X Scale
          default: 100%
        y_scale:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Y Scale
          default: 100%
        flip_horizontal:
          type: boolean
          title: Flip Horizontal
          default: false
        flip_vertical:
          type: boolean
          title: Flip Vertical
          default: false
        opacity:
          type: number
          maximum: 100
          minimum: 0
          title: Opacity
          default: 100
        z_index:
          anyOf:
            - type: integer
            - type: 'null'
          title: Z Index
          description: >-
            Stacking order (higher = on top). When omitted, elements stack in
            array order — later elements render above earlier ones (painter's
            rule); text defaults one layer above non-text. Set explicit values
            only when array order isn't the order you want.
        clip:
          type: boolean
          title: Clip
          default: false
        color_overlay:
          anyOf:
            - type: string
              pattern: ^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$
            - type: 'null'
          title: Color Overlay
          description: >-
            **Not supported by the renderer — setting this is rejected.** A
            strict submit returns `unsupported_feature` (422) and a dry-run
            reports it as a blocking violation; there is no native tint field to
            map it onto. To darken an element use `brightness` (`-100` is a
            black silhouette); to lay a colour over one, stack a `shape` with a
            flat or gradient `fill` above it.
        type:
          type: string
          const: image
          title: Type
          default: image
        id:
          type: string
          minLength: 1
          title: Id
          description: >-
            **Required.** Unique identifier for this image. An empty or missing
            ID causes the image to be silently skipped by the renderer.
          example: logo_01
        name:
          anyOf:
            - type: string
            - type: 'null'
          title: Name
          description: Optional human-readable label. Not used by the renderer.
        track:
          anyOf:
            - type: integer
              maximum: 255
              minimum: 0
            - type: 'null'
          title: Track
          description: >-
            Timeline track index (0–255). Informational only; not used by the
            renderer.
        time:
          anyOf:
            - type: number
              minimum: 0
            - type: 'null'
          title: Time
          description: >-
            When this element starts on the output timeline, in seconds.
            Defaults to `0` when omitted.
          example: 0
        visible:
          type: boolean
          title: Visible
          description: >-
            Set to `false` to skip this element without removing it from the
            request.
          default: true
        duration:
          type: number
          exclusiveMinimum: 0
          title: Duration
          description: >-
            **Required.** Duration in seconds the image is visible. Omitting
            this field produces a zero-length window and the image will not
            appear in the render.
          example: 5
        source_url:
          type: string
          maxLength: 2083
          minLength: 1
          format: uri
          title: Source Url
          description: >-
            URL of the image file (PNG, JPG, WebP, GIF, SVG). `.svg` assets
            rasterize in-process on the renderer — flat fills and stroke icons
            (Lucide/Feather-style) work; gradients, text and filters inside the
            SVG do not (pre-rasterize those to PNG).
          example: https://cdn.example.com/logo.png
        effects:
          items:
            $ref: '#/components/schemas/Effect'
          type: array
          title: Effects
          default: []
        motion:
          items:
            $ref: '#/components/schemas/Motion'
          type: array
          title: Motion
          default: []
      additionalProperties: false
      type: object
      required:
        - id
        - duration
        - source_url
      title: ImageElement
    ShapeElement:
      properties:
        blend_mode:
          $ref: '#/components/schemas/BlendMode'
          default: none
        mask_shape:
          anyOf:
            - $ref: '#/components/schemas/MaskShape'
            - type: 'null'
        mask:
          anyOf:
            - $ref: '#/components/schemas/MaskConfig'
            - type: 'null'
        mask_keyframes:
          anyOf:
            - items:
                $ref: '#/components/schemas/MaskKeyframe'
              type: array
            - type: 'null'
          title: Mask Keyframes
        matte:
          anyOf:
            - $ref: '#/components/schemas/Matte'
            - type: 'null'
        backdrop_blur:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Backdrop Blur
          default: 0
        x:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: X
          default: 50%
        'y':
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: 'Y'
          default: 50%
        width:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Width
          description: >-
            Box width. Together with `height` this is a **cover** box, not a fit
            box: sized media (video, image) scales to fill it and the overflow
            is **cropped** — it is never letterboxed. A box whose aspect ratio
            differs from the source's therefore silently cuts the source's
            edges. To show a source whole, match the box aspect to it: on a
            `W`x`H` canvas, for a source of aspect `a`, `width% = a * height% *
            (H/W)` — e.g. a 1024x1536 asset (a=0.667) on 1920x1080 needs `width%
            = 0.375 * height%`. Percentages are of the canvas, not the parent.
          default: 100%
          example: 40%
        height:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Height
          description: >-
            Box height. See `width` — the pair is a cover box and crops rather
            than letterboxes. Percentages are of the canvas.
          default: 100%
          example: 80%
        aspect_ratio:
          anyOf:
            - type: number
            - type: 'null'
          title: Aspect Ratio
          description: >-
            **Accepted but ignored — the renderer never receives this field.**
            It does NOT constrain or correct the `width`/`height` box, so it
            cannot be used to stop a mismatched box from cropping the source;
            size the box per `width` instead. Kept for backward compatibility.
        x_anchor:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: X Anchor
          description: >-
            Horizontal pivot the element **rotates** about, measured across its
            own box (`0%` = left edge, `50%` = centre, `100%` = right edge; px
            values are an offset from the left edge). This is a pivot, not an
            alignment — `x` always places the element's centre, so the anchor
            changes nothing on an unrotated element. Honored on video, image and
            shape; text and the generator elements rotate about their centre and
            reject a non-default anchor.
          default: 50%
          example: 0%
        y_anchor:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Y Anchor
          description: >-
            Vertical pivot the element **rotates** about, measured across its
            own box (`0%` = top edge, `50%` = centre, `100%` = bottom edge).
            Same rules as `x_anchor`: a pivot, not an alignment.
          default: 50%
          example: 100%
        x_rotation:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: X Rotation
          default: 0°
        y_rotation:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Y Rotation
          default: 0°
        z_rotation:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Z Rotation
          default: 0°
        x_scale:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: X Scale
          default: 100%
        y_scale:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Y Scale
          default: 100%
        flip_horizontal:
          type: boolean
          title: Flip Horizontal
          default: false
        flip_vertical:
          type: boolean
          title: Flip Vertical
          default: false
        opacity:
          type: number
          maximum: 100
          minimum: 0
          title: Opacity
          default: 100
        z_index:
          anyOf:
            - type: integer
            - type: 'null'
          title: Z Index
          description: >-
            Stacking order (higher = on top). When omitted, elements stack in
            array order — later elements render above earlier ones (painter's
            rule); text defaults one layer above non-text. Set explicit values
            only when array order isn't the order you want.
        clip:
          type: boolean
          title: Clip
          default: false
        color_overlay:
          anyOf:
            - type: string
              pattern: ^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$
            - type: 'null'
          title: Color Overlay
          description: >-
            **Not supported by the renderer — setting this is rejected.** A
            strict submit returns `unsupported_feature` (422) and a dry-run
            reports it as a blocking violation; there is no native tint field to
            map it onto. To darken an element use `brightness` (`-100` is a
            black silhouette); to lay a colour over one, stack a `shape` with a
            flat or gradient `fill` above it.
        type:
          type: string
          const: shape
          title: Type
          default: shape
        id:
          type: string
          minLength: 1
          title: Id
          description: '**Required.** Unique identifier.'
          example: blob-1
        name:
          anyOf:
            - type: string
            - type: 'null'
          title: Name
          description: Optional human-readable label. Not used by the renderer.
        track:
          anyOf:
            - type: integer
              maximum: 255
              minimum: 0
            - type: 'null'
          title: Track
          description: >-
            Timeline track index (0–255). Informational only; not used by the
            renderer.
        time:
          anyOf:
            - type: number
              minimum: 0
            - type: 'null'
          title: Time
          description: >-
            When this element starts on the output timeline, in seconds.
            Defaults to `0` when omitted.
          example: 0
        visible:
          type: boolean
          title: Visible
          description: >-
            Set to `false` to skip this element without removing it from the
            request.
          default: true
        duration:
          type: number
          exclusiveMinimum: 0
          title: Duration
          description: '**Required.** Seconds the shape is visible.'
          example: 4
        path:
          type: string
          minLength: 1
          title: Path
          description: SVG path `d` (M L H V C S Q T Z; arcs straighten).
          example: M50 0 L100 100 L0 100 Z
        view_box:
          items:
            type: number
          type: array
          maxItems: 4
          minItems: 4
          title: View Box
          description: '`[x, y, width, height]` path coordinate space.'
          default:
            - 0
            - 0
            - 100
            - 100
        fill:
          anyOf:
            - type: string
              pattern: ^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$
            - type: string
              const: none
            - type: string
              pattern: ^\s*(?:linear|radial)-gradient\s*\(
              description: >-
                CSS gradient shorthand — `linear-gradient(...)` or
                `radial-gradient(...)`. Normalised to the Gradient object during
                validation; the renderer never sees a CSS string, and reading
                the composition back returns the object.
              example: 'linear-gradient(90deg, #ff0000 0%, #0000ff 100%)'
            - $ref: '#/components/schemas/Gradient'
          title: Fill
          description: >-
            Fill: a hex colour (alpha ok), `"none"` for outline-only, a Gradient
            object (`{kind, angle_degrees, center, stops}`, ≥2 stops), or a CSS
            `linear-gradient(...)` / `radial-gradient(...)` string, which is
            normalised to the Gradient object at validation — read back, the
            composition holds the object. A gradient fill on a rectangular path
            is how you draw a gradient card, bar or pill — there is no separate
            `gradient` element. The gradient paints the shape's own path, so it
            fills any silhouette — a triangle, a blob, an icon outline, a
            morphing path — and layers on the same element with `stroke` /
            `stroke_width` / `stroke_dash`, `path_keyframes` (morph), `trim` /
            `trim_keyframes` (draw-on), `blend_mode`, `mask_shape` / `mask` /
            `mask_keyframes`, `matte` and `backdrop_blur`. `fill_rule`,
            `corner_radius` and `opacity` behave with a gradient exactly as they
            do with a flat colour.
          default: '#000000'
          example: 'linear-gradient(90deg, #e07a4f 0%, #7c3aed 100%)'
        fill_rule:
          type: string
          enum:
            - nonzero
            - evenodd
          title: Fill Rule
          default: nonzero
        stroke:
          anyOf:
            - type: string
              pattern: ^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$
            - type: 'null'
          title: Stroke
          description: Stroke colour.
        stroke_width:
          anyOf:
            - type: number
              minimum: 0
            - type: 'null'
          title: Stroke Width
          description: Stroke width in viewBox units (absent = SVG 1.0; 0 disables).
        stroke_dash:
          anyOf:
            - items:
                type: number
              type: array
              maxItems: 2
              minItems: 2
            - type: 'null'
          title: Stroke Dash
          description: >-
            `[on, off]` dash lengths in viewBox units. Trim applies before dash,
            so a dashed path can draw itself on without the pattern re-flowing.
        corner_radius:
          anyOf:
            - type: number
              maximum: 1
              minimum: 0
            - $ref: '#/components/schemas/CornerRadii'
          title: Corner Radius
          description: >-
            Corner radius, normalized 0–1 — a single value or per-corner
            `{top_left, top_right, bottom_right, bottom_left}`.
          default: 0
        shadow:
          anyOf:
            - $ref: '#/components/schemas/DropShadow'
            - type: 'null'
          description: >-
            Optional drop shadow. `distance`/`blur` are the renderer's 0–100
            strength scale, not pixels — see DropShadow.
        path_keyframes:
          anyOf:
            - items:
                $ref: '#/components/schemas/PathKeyframe'
              type: array
            - type: 'null'
          title: Path Keyframes
          description: Morph the path between keyframes (timeline seconds).
        trim:
          anyOf:
            - $ref: '#/components/schemas/TrimPath'
            - type: 'null'
          description: >-
            Static trim window: keep only this arc-length fraction of each
            subpath.
        trim_keyframes:
          anyOf:
            - items:
                $ref: '#/components/schemas/TrimKeyframe'
              type: array
            - type: 'null'
          title: Trim Keyframes
          description: >-
            Animate the trim window (timeline seconds) — the draw-on: animate
            `end` 0→1 on a stroked path to draw a signature, underline or chart
            line. Absent `start`/`end` inherit the static `trim`.
        effects:
          items:
            $ref: '#/components/schemas/Effect'
          type: array
          title: Effects
          description: Shader effect chain (§6 W3).
          default: []
        motion:
          items:
            $ref: '#/components/schemas/Motion'
          type: array
          title: Motion
          default: []
      additionalProperties: false
      type: object
      required:
        - id
        - duration
        - path
      title: ShapeElement
      description: 'A first-class vector primitive: SVG path fill + stroke, morphing (§5b).'
    Transition:
      properties:
        type:
          $ref: '#/components/schemas/TransitionType'
          description: >-
            Transition effect applied between two video elements (transitions
            are video-only).
          example: cross_dissolve
        duration:
          type: number
          exclusiveMinimum: 0
          title: Duration
          description: Transition duration in seconds.
          example: 0.5
        from_id:
          anyOf:
            - type: string
            - type: 'null'
          title: From Id
          description: >-
            ID of the outgoing video element (omit for the very first
            transition).
          example: video_01
        to_id:
          anyOf:
            - type: string
            - type: 'null'
          title: To Id
          description: >-
            ID of the incoming video element (omit for the very last
            transition).
          example: video_02
        z_index:
          anyOf:
            - type: integer
            - type: 'null'
          title: Z Index
          description: >-
            **Deprecated — ignored by the renderer.** The transition draws at
            its linked videos' position; the engine reads no z key on a
            transition. Accepted for backward compatibility only.
          example: 1
      additionalProperties: false
      type: object
      required:
        - type
        - duration
      title: Transition
      description: |-
        A transition between two **video** elements.

        The engine links transitions to video clips only — referencing an image,
        text or other element type is rejected (it would crash the render node).
    Group:
      properties:
        id:
          type: string
          minLength: 1
          title: Id
          description: '**Required.** Unique group id.'
          example: hero
        parent:
          anyOf:
            - type: string
            - type: 'null'
          title: Parent
          description: Parent group id for nesting.
        pivot:
          $ref: '#/components/schemas/Point'
          description: Rotation/scale pivot.
        translate:
          anyOf:
            - $ref: '#/components/schemas/Point'
            - type: 'null'
          description: Group translation (normalized).
        scale:
          type: number
          exclusiveMinimum: 0
          title: Scale
          description: Group scale.
          default: 1
        rotation_degrees:
          type: number
          title: Rotation Degrees
          description: In-plane (z) rotation of the group.
          default: 0
        x_rotation:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: X Rotation
          description: >-
            Perspective tilt (X axis) applied to each member's orientation about
            its own centre — the tilted card-gallery look.
          default: 0°
        y_rotation:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Y Rotation
          description: Perspective tilt (Y axis). See `x_rotation`.
          default: 0°
        opacity:
          type: number
          maximum: 1
          minimum: 0
          title: Opacity
          description: Group opacity.
          default: 1
        members:
          items:
            type: string
          type: array
          title: Members
          description: Member element uuids.
        animations:
          items:
            $ref: '#/components/schemas/Motion'
          type: array
          title: Animations
          description: >-
            Animations driving the whole group about `pivot`. Translate, scale,
            rotation and colour all compose — that is how you move or resize a
            whole scene as one unit. Two channels do NOT: a preset that animates
            **clip** (`wipe_left/right/up/down`) or **crop** (`ken_burns_*`) is
            a silent no-op on a group, doing nothing at all rather than erroring
            — put those on a member element instead. Non-uniform scale is also
            flattened here: only the x component of a `custom_animations`
            `scale` keyframe is used, so `{x:2,y:1}` scales uniformly. Note
            `zoom_out` barely moves (scale 1.0->0.9 under a full fade), so it
            reads as a pure fade; for a real shrink use a `custom` entry with
            the scale travel you want, or the group's static `scale`.
        layout:
          anyOf:
            - $ref: '#/components/schemas/StackLayout'
            - type: 'null'
          description: Optional stack layout.
      additionalProperties: false
      type: object
      required:
        - id
      title: Group
      description: >-
        A transform group over member elements (§5, A6). Composes member → group
        → parent.
    MotionBlur:
      properties:
        samples:
          type: integer
          maximum: 32
          minimum: 2
          title: Samples
          description: Temporal samples per frame (2–32).
        shutter:
          type: number
          maximum: 1
          exclusiveMinimum: 0
          title: Shutter
          description: >-
            Shutter fraction of the frame interval, (0–1]. (Exactly 0 is
            rejected: it renders N identical samples — full cost, zero blur.)
          default: 0.5
      additionalProperties: false
      type: object
      required:
        - samples
      title: MotionBlur
      description: >-
        Shutter-based temporal supersampling of the whole composition (§5c).


        Opt-in and costly: every frame renders `samples` times, and accumulation
        is

        CPU-side, so the render falls back to **software encoding** — expect
        roughly

        `samples`× render time.
    Gradient:
      properties:
        kind:
          type: string
          enum:
            - linear
            - radial
          title: Kind
          default: linear
        angle_degrees:
          type: number
          title: Angle Degrees
          description: >-
            Linear gradient angle in degrees, measured counterclockwise with y
            up (mathematical convention): 0 = left→right (offset 0 at the left),
            45 = bottom-left→top-right, 315 = top-left→bottom-right, and the
            same convention governs a shape's gradient `fill`. A numeric CSS
            angle is box-relative: `135deg` behaves as `to bottom right`
            whatever the card's shape; only 0/90/180/270 are pixel-exact.
          default: 0
        center:
          anyOf:
            - $ref: '#/components/schemas/Point'
            - type: 'null'
          description: >-
            Radial focus point as fractions of the fill's box — the element box
            for a shape's gradient fill, the canvas for the background. `y` runs
            bottom-up (`{x: 0.5, y: 0}` is the bottom edge), the opposite of CSS
            `at 50% 100%`. Defaults to the centre; ignored for linear gradients.
            The renderer's radial is an ellipse inscribed in the box, reaching
            the last stop at the corners: it keeps that centred radius when the
            focus moves, rather than re-deriving it from the focus the way CSS
            `farthest-corner` does, so an off-centre focus imported from CSS
            completes its ramp earlier than a browser shows it. A CSS `circle`
            is drawn as that same ellipse, so it only comes out round on a
            square box.
        stops:
          items:
            $ref: '#/components/schemas/GradientStop'
          type: array
          minItems: 2
          title: Stops
          description: Two or more colour stops.
      additionalProperties: false
      type: object
      required:
        - stops
      title: Gradient
    CustomAnimationRegistry:
      properties:
        element:
          items:
            $ref: '#/components/schemas/CustomAnimation'
          type: array
          title: Element
          default: []
        text:
          items:
            $ref: '#/components/schemas/CustomAnimation'
          type: array
          title: Text
          default: []
      additionalProperties: false
      type: object
      title: CustomAnimationRegistry
      description: >-
        Inline custom-animation registries for element and text animations (§3).


        `element` entries are bindable from any video/image/shape/group

        animation via `motion[].custom` (or `animations[].custom` on groups).

        `text` entries bind from a text element's `animation_preset` or

        `motion[].custom`.


        Names that shadow a catalog preset — or the reserved static keys `none`/

        `overlay`/`difference` — are rejected here: the engine
        warn-and-*ignores*

        such a definition (renderer_node.cpp:1500-1516) and the reference then

        silently resolves to the catalog preset instead of yours.
    ApiErrorCode:
      description: >-
        Machine-readable error code returned on a 4xx/5xx response. Branch on
        `error.code` to

        handle failures programmatically.


        Values:

        - `invalid_request` — The request was malformed or failed validation.

        - `unauthorized` — Missing or invalid API key.

        - `forbidden` — Authenticated but not permitted to perform this action.

        - `not_found` — The requested resource does not exist.

        - `conflict` — Conflicts with current state (e.g. a reused
        Idempotency-Key with a different body).

        - `unsupported_feature` — The composition uses a feature the renderer
        does not support.

        - `invalid_source` — A source URL is malformed or unreachable.

        - `source_not_found` — A referenced source file could not be found.

        - `source_too_large` — A source file exceeds the allowed size.

        - `asset_not_ready` — A referenced asset is still ingesting and is not
        ready yet.

        - `ingest_failed` — Copying an external source into Framelane storage
        failed.

        - `codec_unsupported` — A source uses a codec the renderer cannot
        decode.

        - `quota_exceeded` — The workspace has exhausted its plan quota (HTTP
        402).

        - `rate_limited` — Too many requests; retry after the Retry-After
        interval.

        - `internal` — An unexpected server error occurred.

        - `email_not_verified` — The workspace email is unverified; verify it
        before making authed calls.

        - `email_in_use` — The email is already associated with another
        workspace.

        - `invalid_otp` — The verification code is incorrect or expired.

        - `unsupported_content_type` — The provided content type is not
        supported.

        - `http_error` — A generic HTTP error not covered by a more specific
        code.
      enum:
        - invalid_request
        - unauthorized
        - forbidden
        - not_found
        - conflict
        - unsupported_feature
        - invalid_source
        - source_not_found
        - source_too_large
        - asset_not_ready
        - ingest_failed
        - codec_unsupported
        - quota_exceeded
        - rate_limited
        - internal
        - email_not_verified
        - email_in_use
        - invalid_otp
        - unsupported_content_type
        - http_error
      title: ErrorCode
      type: string
      x-enumDescriptions:
        invalid_request: The request was malformed or failed validation.
        unauthorized: Missing or invalid API key.
        forbidden: Authenticated but not permitted to perform this action.
        not_found: The requested resource does not exist.
        conflict: >-
          Conflicts with current state (e.g. a reused Idempotency-Key with a
          different body).
        unsupported_feature: The composition uses a feature the renderer does not support.
        invalid_source: A source URL is malformed or unreachable.
        source_not_found: A referenced source file could not be found.
        source_too_large: A source file exceeds the allowed size.
        asset_not_ready: A referenced asset is still ingesting and is not ready yet.
        ingest_failed: Copying an external source into Framelane storage failed.
        codec_unsupported: A source uses a codec the renderer cannot decode.
        quota_exceeded: The workspace has exhausted its plan quota (HTTP 402).
        rate_limited: Too many requests; retry after the Retry-After interval.
        internal: An unexpected server error occurred.
        email_not_verified: >-
          The workspace email is unverified; verify it before making authed
          calls.
        email_in_use: The email is already associated with another workspace.
        invalid_otp: The verification code is incorrect or expired.
        unsupported_content_type: The provided content type is not supported.
        http_error: A generic HTTP error not covered by a more specific code.
    CornerRadiiPx:
      properties:
        top_left:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Top Left
          default: 0
        top_right:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Top Right
          default: 0
        bottom_right:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Bottom Right
          default: 0
        bottom_left:
          anyOf:
            - type: integer
            - type: number
            - type: string
          title: Bottom Left
          default: 0
      additionalProperties: false
      type: object
      title: CornerRadiiPx
      description: >-
        Per-corner radii in pixels (for `border_radius` on video/image/text
        elements).
    BlendMode:
      type: string
      enum:
        - none
        - multiply
        - screen
        - overlay
        - darken
        - lighten
        - color_dodge
        - color_burn
        - hard_light
        - soft_light
        - difference
        - add
        - exclusion
        - hue
        - saturation
        - color
        - luminosity
      title: BlendMode
      description: >-
        Values:

        - `none` — Normal compositing (source-over) — the default.

        - `multiply` — Multiply blend — darkens by multiplying layer colors.

        - `screen` — Screen blend — lightens by inverting, multiplying,
        inverting.

        - `overlay` — Overlay blend — multiply in shadows, screen in highlights.

        - `darken` — Darken blend — keeps the darker of the two layers per
        channel.

        - `lighten` — Lighten blend — keeps the lighter of the two layers per
        channel.

        - `color_dodge` — Color-dodge blend — brightens the base toward the
        blend color.

        - `color_burn` — Color-burn blend — darkens the base toward the blend
        color.

        - `hard_light` — Hard-light blend — overlay with layers swapped.

        - `soft_light` — Soft-light blend — a gentler hard-light.

        - `difference` — Difference blend — absolute difference of the two
        layers per channel.

        - `add` — Additive (linear-dodge) blend — sums layer colors, clamped.

        - `exclusion` — Exclusion blend — like difference, lower contrast.

        - `hue` — Hue blend — backdrop luminosity + saturation with the blend
        layer's hue.

        - `saturation` — Saturation blend — backdrop hue + luminosity with the
        blend layer's saturation.

        - `color` — Color blend — backdrop luminosity with the blend layer's hue
        + saturation.

        - `luminosity` — Luminosity blend — backdrop hue + saturation with the
        blend layer's luminosity.
      x-enumDescriptions:
        none: Normal compositing (source-over) — the default.
        multiply: Multiply blend — darkens by multiplying layer colors.
        screen: Screen blend — lightens by inverting, multiplying, inverting.
        overlay: Overlay blend — multiply in shadows, screen in highlights.
        darken: Darken blend — keeps the darker of the two layers per channel.
        lighten: Lighten blend — keeps the lighter of the two layers per channel.
        color_dodge: Color-dodge blend — brightens the base toward the blend color.
        color_burn: Color-burn blend — darkens the base toward the blend color.
        hard_light: Hard-light blend — overlay with layers swapped.
        soft_light: Soft-light blend — a gentler hard-light.
        difference: Difference blend — absolute difference of the two layers per channel.
        add: Additive (linear-dodge) blend — sums layer colors, clamped.
        exclusion: Exclusion blend — like difference, lower contrast.
        hue: >-
          Hue blend — backdrop luminosity + saturation with the blend layer's
          hue.
        saturation: >-
          Saturation blend — backdrop hue + luminosity with the blend layer's
          saturation.
        color: >-
          Color blend — backdrop luminosity with the blend layer's hue +
          saturation.
        luminosity: >-
          Luminosity blend — backdrop hue + saturation with the blend layer's
          luminosity.
    MaskShape:
      type: string
      enum:
        - circle
        - diamond
        - hexagon
        - star
        - heart
        - triangle
      title: MaskShape
      description: >-
        Analytic (SDF) shape mask that clips an element's coverage. §1.


        Values:

        - `circle` — Circular (ellipse) analytic mask, fit to the element
        bounds.

        - `diamond` — Diamond (rotated square) analytic mask.

        - `hexagon` — Regular hexagon analytic mask.

        - `star` — Five-point star analytic mask.

        - `heart` — Heart analytic mask.

        - `triangle` — Upward triangle analytic mask.
      x-enumDescriptions:
        circle: Circular (ellipse) analytic mask, fit to the element bounds.
        diamond: Diamond (rotated square) analytic mask.
        hexagon: Regular hexagon analytic mask.
        star: Five-point star analytic mask.
        heart: Heart analytic mask.
        triangle: Upward triangle analytic mask.
    MaskConfig:
      properties:
        feather:
          type: number
          minimum: 0
          title: Feather
          description: Soften the mask's SDF edge.
          default: 0
        scale:
          type: number
          exclusiveMinimum: 0
          title: Scale
          description: Mask scale (iris reveals).
          default: 1
        center:
          anyOf:
            - $ref: '#/components/schemas/Point'
            - type: 'null'
          description: Mask centre (wipes).
      additionalProperties: false
      type: object
      title: MaskConfig
      description: >-
        Animated/feathered mask params layered on the analytic `mask_shape`
        (§5c).
    MaskKeyframe:
      properties:
        time:
          type: number
          minimum: 0
          title: Time
          description: Timeline seconds.
        feather:
          anyOf:
            - type: number
            - type: 'null'
          title: Feather
        scale:
          anyOf:
            - type: number
            - type: 'null'
          title: Scale
        center:
          anyOf:
            - $ref: '#/components/schemas/Point'
            - type: 'null'
        easing:
          anyOf:
            - type: string
            - type: 'null'
          title: Easing
          description: >-
            Easing curve name — any `easings` catalog value (`linear`, `hold`,
            `ease_in_out`, `quad_out`, `expo_in_out`, `square_in_out` = step,
            …). Unknown names are rejected: the engine would silently fall back
            to linear. For spring/back/elastic/bezier use the sub-objects
            instead.
        spring:
          anyOf:
            - $ref: '#/components/schemas/SpringEasing'
            - type: 'null'
        back:
          anyOf:
            - $ref: '#/components/schemas/BackEasing'
            - type: 'null'
        elastic:
          anyOf:
            - $ref: '#/components/schemas/ElasticEasing'
            - type: 'null'
        bezier:
          anyOf:
            - $ref: '#/components/schemas/BezierEasing'
            - type: 'null'
      additionalProperties: false
      type: object
      required:
        - time
      title: MaskKeyframe
      description: >-
        A keyframe for animated masks (§5c). Absent fields inherit the static
        mask.
    Matte:
      properties:
        source:
          type: string
          minLength: 1
          title: Source
          description: uuid of the source layer (auto-hidden). Text sources supported.
        mode:
          type: string
          enum:
            - alpha
            - luma
          title: Mode
          default: alpha
        invert:
          type: boolean
          title: Invert
          default: false
      additionalProperties: false
      type: object
      required:
        - source
      title: Matte
      description: >-
        Track matte (§5d): drive this element's coverage from another layer's
        render.
    RateKeyframe:
      properties:
        time:
          type: number
          minimum: 0
          title: Time
          description: Timeline seconds.
        rate:
          type: number
          minimum: 0
          title: Rate
          description: Playback rate (0 = freeze-frame).
      additionalProperties: false
      type: object
      required:
        - time
        - rate
      title: RateKeyframe
      description: A point on a video speed ramp (§5c). Absolute timeline seconds.
    Effect:
      properties:
        type:
          $ref: '#/components/schemas/EffectType'
        intensity:
          type: number
          maximum: 100
          minimum: 0
          title: Intensity
          default: 50
        intensity_keyframes:
          anyOf:
            - items:
                $ref: '#/components/schemas/IntensityKeyframe'
              type: array
            - type: 'null'
          title: Intensity Keyframes
          description: >-
            Animate intensity over time (§2): piecewise-linear `[{time, value}]`
            in timeline seconds. Overrides the static `intensity` when set.
        props:
          anyOf:
            - additionalProperties:
                type: number
              type: object
            - type: 'null'
          title: Props
          description: >-
            Per-shader scalar props (§2), bound as `u_prop_<key>` (e.g.
            `{"speed": 0.6}` for aurora). Ignored for chroma_key (use
            `chroma_settings`).
        chroma_settings:
          anyOf:
            - $ref: '#/components/schemas/ChromaKeyProps'
            - type: 'null'
      additionalProperties: false
      type: object
      required:
        - type
      title: Effect
    Motion:
      properties:
        type:
          anyOf:
            - $ref: '#/components/schemas/MotionType'
            - type: 'null'
          description: Catalog preset. Exactly one of `type` or `custom` must be set.
        custom:
          anyOf:
            - type: string
            - type: 'null'
          title: Custom
          description: >-
            Name of a `custom_animations` entry to run instead of a catalog
            preset — the `element` registry for video/image/shape/group
            animations, the `text` registry for text. Exactly one of `type` or
            `custom` must be set.
        time:
          type: number
          minimum: 0
          title: Time
          description: >-
            Absolute start time on the output timeline in seconds. For entrances
            and loops set this to the element's `time`. For exits set this to
            element `time + duration − motion duration`. An entrance timed later
            than the element's own `time` leaves it fully rendered on screen
            until the entrance fires; the linter reports that as
            `ENTRANCE_AFTER_ELEMENT_START`.
        duration:
          type: number
          exclusiveMinimum: 0
          title: Duration
          description: How long the animation runs in seconds.
        easing:
          $ref: '#/components/schemas/Easing'
          description: >-
            **Ignored by the renderer** — animation curves are baked into the
            preset's keyframes (or your `custom_animations` keyframes'
            `easing`). Kept for backward compatibility only.
          default: ease_in_out
        reversed:
          type: boolean
          title: Reversed
          description: >-
            Run the preset's exit form instead of its entrance. Only presets
            that ship both forms accept it — `fade`, `slide_*`, `wipe_*`,
            `rotate_*`, `bounce`. Entrance-only presets have a separate
            exit-only type instead: use `zoom_out` (not `zoom_in` reversed),
            `evaporate` (not `blur`), `whip_down`, `drift_out`, `swing_out`,
            `elastic_drop` — those six set this flag for you. See `GET
            /v1/capabilities` `motion[].element_exit` for the per-preset truth.
          default: false
        scope:
          $ref: '#/components/schemas/MotionScope'
          default: element
        delay:
          type: number
          minimum: 0
          title: Delay
          description: >-
            Seconds to wait before the animation starts (folded into the
            animation's start time on the wire — the engine has no standalone
            pre-start delay). On loop presets it spaces the repeats instead.
          default: 0
        spacing:
          anyOf:
            - type: number
              minimum: 0
            - type: 'null'
          title: Spacing
          description: >-
            Stagger multiplier between per-glyph/word windows on text animations
            (engine default 1 = fully sequential; smaller values overlap the
            windows). Ignored by block animations.
        loop:
          type: boolean
          title: Loop
          default: false
        audio:
          anyOf:
            - $ref: '#/components/schemas/AudioGenerator'
            - type: 'null'
          description: >-
            Audio-reactive offsets (§3). Element animations only
            (video/image/shape/group) — the engine does not run the generator on
            text animations; put the text in an audio-reactive group instead.
            For a pure generator with no visible motion, use `type: "identity"`,
            the engine's no-op carrier.
      additionalProperties: false
      type: object
      required:
        - time
        - duration
      title: Motion
    WordAnimation:
      properties:
        style:
          $ref: '#/components/schemas/WordAnimationStyle'
        words:
          items:
            $ref: '#/components/schemas/Word'
          type: array
          minItems: 1
          title: Words
      additionalProperties: false
      type: object
      required:
        - style
        - words
      title: WordAnimation
    WordSpec:
      properties:
        word:
          type: string
          minLength: 1
          title: Word
          description: The word (whole-word match; UTF-8).
        start:
          type: number
          minimum: 0
          title: Start
          description: >-
            Word reveal start, seconds — drives a word-group animation; ignored
            without one.
        end:
          type: number
          exclusiveMinimum: 0
          title: End
          description: >-
            Word reveal end, seconds — drives a word-group animation; ignored
            without one.
        style:
          anyOf:
            - $ref: '#/components/schemas/WordStyle'
            - type: 'null'
          description: Optional per-word style override (static for the element's life).
      additionalProperties: false
      type: object
      required:
        - word
        - start
        - end
      title: WordSpec
      description: |-
        Per-word timing + optional style, keyed by word text (§4).

        ``style`` overrides are static for the element's whole life. ``start``/
        ``end`` drive **word-group animations only** — bind one via
        ``word_animation`` (or a word-group ``animation_preset``) or the timings
        have nothing to time and are ignored by the renderer.
    ValueCounter:
      properties:
        from_value:
          type: number
          title: From Value
          description: Start value.
          default: 0
        to_value:
          type: number
          title: To Value
          description: End value.
          default: 100
        decimals:
          type: integer
          maximum: 6
          minimum: 0
          title: Decimals
          description: Decimal places (0–6).
          default: 0
        prefix:
          type: string
          title: Prefix
          description: Text prepended to the number.
          default: ''
        suffix:
          type: string
          title: Suffix
          description: Text appended to the number, e.g. '%'.
          default: ''
        easing:
          anyOf:
            - type: string
            - type: 'null'
          title: Easing
          description: >-
            Easing curve name from the `easings` catalog (e.g. 'quad_out',
            'expo_out'). Only named curves apply here — the parameterized
            spring/back/elastic families are not available on the counter.
        start_time:
          type: number
          minimum: 0
          title: Start Time
          description: Seconds into the element to start the sweep.
          default: 0
        length:
          anyOf:
            - type: number
              exclusiveMinimum: 0
            - type: 'null'
          title: Length
          description: Sweep duration; defaults to the element's remaining life.
      additionalProperties: false
      type: object
      title: ValueCounter
      description: >-
        Rewrite a text element from an eased numeric sweep (the animated stat
        hero, §4).
    TextGlow:
      properties:
        color:
          type: string
          pattern: ^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$
          title: Color
          example: '#ffffff'
        size:
          type: number
          minimum: 0
          title: Size
          description: Glow size, em-relative.
          default: 0.15
        intensity:
          type: number
          minimum: 0
          title: Intensity
          description: Glow intensity.
          default: 1
      additionalProperties: false
      type: object
      required:
        - color
      title: TextGlow
      description: Soft glow around the glyphs (§4).
    CornerRadii:
      properties:
        top_left:
          type: number
          maximum: 1
          minimum: 0
          title: Top Left
          default: 0
        top_right:
          type: number
          maximum: 1
          minimum: 0
          title: Top Right
          default: 0
        bottom_right:
          type: number
          maximum: 1
          minimum: 0
          title: Bottom Right
          default: 0
        bottom_left:
          type: number
          maximum: 1
          minimum: 0
          title: Bottom Left
          default: 0
      additionalProperties: false
      type: object
      title: CornerRadii
      description: >-
        Per-corner radii, each normalized 0–1 (1 = fully round on the shorter
        side).
    DropShadow:
      properties:
        color:
          type: string
          pattern: ^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$
          title: Color
          example: '#00000066'
        distance:
          type: number
          minimum: 0
          title: Distance
          description: >-
            Shadow offset **strength**, 0–100 on the renderer's own scale —
            **not pixels**. The rendered offset is `distance × 0.0005 × the
            element's shorter side`, so on a 400px-tall card `60` drops the
            shadow 12px while a px-looking `8` drops it 1.6px, and on a 200px
            chip that same `8` is 0.8px — invisible. Author 60–70 (what the
            reference scenes use) and it scales with the element; for a specific
            N px offset use `distance = N ÷ (0.0005 × shorter_side_px)`. Must be
            > 0: distance 0 is skipped by the renderer entirely. For pixel-unit
            shadows use `shadow_x`/`shadow_y` on video/image elements.
          default: 0
        direction:
          type: number
          title: Direction
          description: Shadow direction in degrees (polar; 90 = down).
          default: 90
        blur:
          type: number
          minimum: 0
          title: Blur
          description: >-
            Shadow softness, 0–100 on the renderer's blur-strength scale —
            **not** a pixel radius, and unlike `distance` it does not scale with
            the element. `0` is a hard-edged offset copy (a legitimate poster
            look); 60–70 is the soft card shadow the reference scenes author. A
            px-looking `8` is a near-hard edge, not an 8px feather.
          default: 0
      additionalProperties: false
      type: object
      required:
        - color
      title: DropShadow
      description: >-
        Drop shadow for shape elements, in the renderer's own units.


        ``distance`` and ``blur`` are the engine's unitless 0–100 strength
        scale,

        NOT pixels — a px-looking ``distance: 10`` is a fraction of what you
        meant

        on a small element and renders as nothing at all. The offset lands at

        ``distance × 0.0005 × the element's shorter side``; the reference scenes

        author 60–70 for both. For pixel-unit shadows use
        ``shadow_x``/``shadow_y``

        on video and image elements. The layout lint reports a distance that

        renders under a pixel as ``SHADOW_BELOW_MIN_SIZE``.
    PathKeyframe:
      properties:
        time:
          type: number
          minimum: 0
          title: Time
          description: Timeline seconds for this path.
        d:
          type: string
          minLength: 1
          title: D
          description: SVG path `d` at this time.
        easing:
          anyOf:
            - $ref: '#/components/schemas/Easing'
            - type: 'null'
          description: >-
            Ease into this keyframe. A spring/back/elastic/bezier sub-object
            overrides it — overshooting eases visibly overshoot the *geometry*
            and settle back.
        spring:
          anyOf:
            - $ref: '#/components/schemas/SpringEasing'
            - type: 'null'
        back:
          anyOf:
            - $ref: '#/components/schemas/BackEasing'
            - type: 'null'
        elastic:
          anyOf:
            - $ref: '#/components/schemas/ElasticEasing'
            - type: 'null'
        bezier:
          anyOf:
            - $ref: '#/components/schemas/BezierEasing'
            - type: 'null'
      additionalProperties: false
      type: object
      required:
        - time
        - d
      title: PathKeyframe
    TrimPath:
      properties:
        start:
          type: number
          maximum: 1
          minimum: 0
          title: Start
          description: Trim start, 0–1 along the path.
          default: 0
        end:
          type: number
          maximum: 1
          minimum: 0
          title: End
          description: Trim end, 0–1 along the path.
          default: 1
      additionalProperties: false
      type: object
      title: TrimPath
    TrimKeyframe:
      properties:
        time:
          type: number
          minimum: 0
          title: Time
          description: Timeline seconds.
        start:
          anyOf:
            - type: number
              maximum: 1
              minimum: 0
            - type: 'null'
          title: Start
        end:
          anyOf:
            - type: number
              maximum: 1
              minimum: 0
            - type: 'null'
          title: End
        easing:
          anyOf:
            - $ref: '#/components/schemas/Easing'
            - type: 'null'
          description: Ease into this keyframe.
        spring:
          anyOf:
            - $ref: '#/components/schemas/SpringEasing'
            - type: 'null'
        back:
          anyOf:
            - $ref: '#/components/schemas/BackEasing'
            - type: 'null'
        elastic:
          anyOf:
            - $ref: '#/components/schemas/ElasticEasing'
            - type: 'null'
        bezier:
          anyOf:
            - $ref: '#/components/schemas/BezierEasing'
            - type: 'null'
      additionalProperties: false
      type: object
      required:
        - time
      title: TrimKeyframe
      description: >-
        A keyframe on the trim window (draw-on, §5b).


        Absent ``start``/``end`` inherit the static ``trim`` values, so
        animating

        ``end`` 0→1 on a stroked path draws it on while ``start`` stays put.
    TransitionType:
      type: string
      enum:
        - cross_dissolve
        - dip_to_black
        - dip_to_white
        - wipe_left
        - wipe_right
        - wipe_up
        - wipe_down
        - diagonal_wipe
        - barn_doors_horizontal
        - barn_doors_vertical
        - iris
        - page_turn
        - cross_zoom
        - gradient_wipe
        - band_wipe
        - box_wipe
        - swirl
        - glitch_memories
        - window_slice
        - cube
        - doorway
        - pinwheel
        - water_drop
        - crosshatch
        - dreamy
        - angular
        - burn
        - heart
        - circle_open
        - color_phase
        - squares_wire
        - whip_pan
        - cinematic_zoom
        - burn_edge
        - shatter
        - domain_warp
        - circle_crop
        - cross_warp
        - fold_horizontal
        - fold_vertical
        - linear_blur
        - minimise_bottom_left
        - minimise_bottom_right
        - minimise_top_left
        - minimise_top_right
        - ripple
        - rotate
        - splice
        - three_stripes
      title: TransitionType
      description: >-
        Values:

        - `cross_dissolve` — Cross-dissolve — fades one clip into the next.

        - `dip_to_black` — Dips through black between clips.

        - `dip_to_white` — Dips through white between clips.

        - `wipe_left` — Wipes the next clip in from the right to the left.

        - `wipe_right` — Wipes the next clip in from the left to the right.

        - `wipe_up` — Wipes the next clip in from the bottom upward.

        - `wipe_down` — Wipes the next clip in from the top downward.

        - `diagonal_wipe` — Wipes across on a diagonal split.

        - `barn_doors_horizontal` — Two halves part horizontally like barn
        doors.

        - `barn_doors_vertical` — Two halves part vertically like barn doors.

        - `iris` — Circular iris (bullseye) open/close between clips.

        - `page_turn` — Page-flip / page-curl reveal.

        - `cross_zoom` — Zoom-blur push from one clip into the next.

        - `gradient_wipe` — Soft gradient-driven dissolve wipe.

        - `band_wipe` — Interleaved stripes wipe the next clip in.

        - `box_wipe` — Rectangular box grows to reveal the next clip.

        - `swirl` — Swirls the outgoing clip away in a spiral.

        - `glitch_memories` — Glitchy, memory-like distortion between clips.

        - `window_slice` — Slices the frame into sliding window strips.

        - `cube` — 3D cube rotation between the two clips.

        - `doorway` — Clips part like opening doors to reveal the next.

        - `pinwheel` — Pinwheel rotation wipe between clips.

        - `water_drop` — Rippling water-drop distortion reveals the next clip.

        - `crosshatch` — Crosshatch pattern dissolve between clips.

        - `dreamy` — Soft, dreamy blur-dissolve between clips.

        - `angular` — Angular wedge sweep between clips.

        - `burn` — Burning-film dissolve between clips.

        - `heart` — Heart-shaped iris reveal of the next clip.

        - `circle_open` — Expanding circle opens onto the next clip.

        - `color_phase` — Color-phase shift blends between clips.

        - `squares_wire` — Wireframe squares wipe between clips.

        - `whip_pan` — Directional motion-blur whip pan.

        - `cinematic_zoom` — Punch zoom with radial blur + chromatic fringing.

        - `burn_edge` — Noise-ridged burn front with ember glow and sparks.

        - `shatter` — Jittered shards fly apart revealing the incoming clip.

        - `domain_warp` — Organic noise-warped dissolve with an emissive
        frontier.

        - `circle_crop` — Both clips crop to a shrinking/growing circle through
        the cut.

        - `cross_warp` — Clips warp into each other along a dissolving
        threshold.

        - `fold_horizontal` — Outgoing clip folds away horizontally, revealing
        the incoming.

        - `fold_vertical` — Outgoing clip folds away vertically, revealing the
        incoming.

        - `linear_blur` — Cross-dissolve through a heavy directional blur peak.

        - `minimise_bottom_left` — Outgoing clip shrinks away into the
        bottom-left corner.

        - `minimise_bottom_right` — Outgoing clip shrinks away into the
        bottom-right corner.

        - `minimise_top_left` — Outgoing clip shrinks away into the top-left
        corner.

        - `minimise_top_right` — Outgoing clip shrinks away into the top-right
        corner.

        - `ripple` — Water-ripple distortion sweeps the cut across the frame.

        - `rotate` — Both clips rotate about the frame centre through the cut.

        - `splice` — Angled splice slides the incoming clip over the outgoing.

        - `three_stripes` — Three offset stripes sweep the incoming clip in.
      x-enumDescriptions:
        cross_dissolve: Cross-dissolve — fades one clip into the next.
        dip_to_black: Dips through black between clips.
        dip_to_white: Dips through white between clips.
        wipe_left: Wipes the next clip in from the right to the left.
        wipe_right: Wipes the next clip in from the left to the right.
        wipe_up: Wipes the next clip in from the bottom upward.
        wipe_down: Wipes the next clip in from the top downward.
        diagonal_wipe: Wipes across on a diagonal split.
        barn_doors_horizontal: Two halves part horizontally like barn doors.
        barn_doors_vertical: Two halves part vertically like barn doors.
        iris: Circular iris (bullseye) open/close between clips.
        page_turn: Page-flip / page-curl reveal.
        cross_zoom: Zoom-blur push from one clip into the next.
        gradient_wipe: Soft gradient-driven dissolve wipe.
        band_wipe: Interleaved stripes wipe the next clip in.
        box_wipe: Rectangular box grows to reveal the next clip.
        swirl: Swirls the outgoing clip away in a spiral.
        glitch_memories: Glitchy, memory-like distortion between clips.
        window_slice: Slices the frame into sliding window strips.
        cube: 3D cube rotation between the two clips.
        doorway: Clips part like opening doors to reveal the next.
        pinwheel: Pinwheel rotation wipe between clips.
        water_drop: Rippling water-drop distortion reveals the next clip.
        crosshatch: Crosshatch pattern dissolve between clips.
        dreamy: Soft, dreamy blur-dissolve between clips.
        angular: Angular wedge sweep between clips.
        burn: Burning-film dissolve between clips.
        heart: Heart-shaped iris reveal of the next clip.
        circle_open: Expanding circle opens onto the next clip.
        color_phase: Color-phase shift blends between clips.
        squares_wire: Wireframe squares wipe between clips.
        whip_pan: Directional motion-blur whip pan.
        cinematic_zoom: Punch zoom with radial blur + chromatic fringing.
        burn_edge: Noise-ridged burn front with ember glow and sparks.
        shatter: Jittered shards fly apart revealing the incoming clip.
        domain_warp: Organic noise-warped dissolve with an emissive frontier.
        circle_crop: Both clips crop to a shrinking/growing circle through the cut.
        cross_warp: Clips warp into each other along a dissolving threshold.
        fold_horizontal: Outgoing clip folds away horizontally, revealing the incoming.
        fold_vertical: Outgoing clip folds away vertically, revealing the incoming.
        linear_blur: Cross-dissolve through a heavy directional blur peak.
        minimise_bottom_left: Outgoing clip shrinks away into the bottom-left corner.
        minimise_bottom_right: Outgoing clip shrinks away into the bottom-right corner.
        minimise_top_left: Outgoing clip shrinks away into the top-left corner.
        minimise_top_right: Outgoing clip shrinks away into the top-right corner.
        ripple: Water-ripple distortion sweeps the cut across the frame.
        rotate: Both clips rotate about the frame centre through the cut.
        splice: Angled splice slides the incoming clip over the outgoing.
        three_stripes: Three offset stripes sweep the incoming clip in.
    Point:
      properties:
        x:
          type: number
          title: X
          default: 0.5
        'y':
          type: number
          title: 'Y'
          default: 0.5
      additionalProperties: false
      type: object
      title: Point
    StackLayout:
      properties:
        type:
          type: string
          const: stack
          title: Type
          default: stack
        direction:
          type: string
          enum:
            - row
            - column
          title: Direction
          default: row
        gap:
          type: number
          title: Gap
          description: Gap between members, normalized along the main axis.
          default: 0
        align:
          type: string
          enum:
            - start
            - center
            - end
          title: Align
          default: center
        origin:
          anyOf:
            - $ref: '#/components/schemas/Point'
            - type: 'null'
          description: Centre of the whole stack.
      additionalProperties: false
      type: object
      title: StackLayout
      description: Resolve members into a row/column stack (§5, A2).
    GradientStop:
      properties:
        offset:
          type: number
          maximum: 1
          minimum: 0
          title: Offset
          description: Position along the gradient, 0–1.
        color:
          type: string
          pattern: ^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$
          title: Color
          description: Stop colour (hex, alpha supported).
          example: '#e07a4f'
      additionalProperties: false
      type: object
      required:
        - offset
        - color
      title: GradientStop
    CustomAnimation:
      properties:
        name:
          type: string
          minLength: 1
          title: Name
          description: Reference name (reserved/catalog-shadowing names are rejected).
        keyframes:
          items:
            $ref: '#/components/schemas/AnimationKeyframe'
          type: array
          minItems: 1
          title: Keyframes
        group:
          type: string
          enum:
            - block
            - word
            - glyph
          title: Group
          description: >-
            What the animation repeats over: the whole text block, each word, or
            each glyph. Required for `selector` to shape anything — a selector
            over a single block has nothing to range across. Text registries
            only.
          default: block
        selector:
          anyOf:
            - $ref: '#/components/schemas/RangeSelector'
            - type: 'null'
          description: >-
            AE-style range selector: shape this animation's amount by position
            along the text (§4, W2). Set `group` to `word` or `glyph` alongside
            it. Text registries only.
      additionalProperties: false
      type: object
      required:
        - name
        - keyframes
      title: CustomAnimation
      description: >-
        A task-supplied keyframe animation, referenced by `name` from an
        animation entry (§3).
    SpringEasing:
      properties:
        stiffness:
          type: number
          exclusiveMinimum: 0
          title: Stiffness
          description: Spring stiffness.
          default: 100
        damping:
          type: number
          exclusiveMinimum: 0
          title: Damping
          description: Damping; lower rings longer.
          default: 10
        mass:
          type: number
          exclusiveMinimum: 0
          title: Mass
          description: Mass; higher swings slower.
          default: 1
      additionalProperties: false
      type: object
      title: SpringEasing
      description: Critically-damped-style spring ease; overshoots and settles.
    BackEasing:
      properties:
        overshoot:
          type: number
          title: Overshoot
          description: Overshoot amount; AE default 1.70158.
          default: 1.70158
        dir:
          type: string
          enum:
            - in
            - out
          title: Dir
          description: '''in'' anticipates, ''out'' overshoots.'
          default: out
      additionalProperties: false
      type: object
      title: BackEasing
      description: Anticipate/overshoot ease (AE 'back').
    ElasticEasing:
      properties:
        amplitude:
          type: number
          title: Amplitude
          description: Ring amplitude.
          default: 1
        period:
          type: number
          exclusiveMinimum: 0
          title: Period
          description: Ring period as a fraction of the segment.
          default: 0.3
        dir:
          type: string
          enum:
            - in
            - out
          title: Dir
          description: '''in'' rings before, ''out'' after.'
          default: out
      additionalProperties: false
      type: object
      title: ElasticEasing
      description: Elastic ring-out/ring-in ease.
    BezierEasing:
      properties:
        x1:
          type: number
          title: X1
          default: 0
        y1:
          type: number
          title: Y1
          default: 0
        x2:
          type: number
          title: X2
          default: 1
        y2:
          type: number
          title: Y2
          default: 1
      additionalProperties: false
      type: object
      title: BezierEasing
      description: CSS cubic-bezier(x1, y1, x2, y2) timing curve.
    EffectType:
      type: string
      enum:
        - vintage
        - polaroid
        - portra
        - super8
        - filmic
        - add_grain
        - rgb_split
        - ghosting
        - displacement_map
        - posterize
        - mosaic
        - mosaic_blur
        - mosaic_posterize
        - cc_halftone
        - cc_halftone_blue
        - cc_halftone_green
        - cc_halftone_red
        - invert
        - optics_compensation
        - viewfinder
        - night_vision
        - thermal
        - camera_lens_blur
        - camera_lens_blur_bg
        - box_blur
        - lens_flare
        - strobe_light
        - snow
        - glow
        - sepia
        - echo
        - chromatic_aberration
        - vhs
        - vhs_overlay
        - crt
        - television
        - glitch
        - compression_glitch
        - scanlines
        - prism
        - light_leaks
        - film_burn
        - duotone
        - cross_process
        - bleach_bypass
        - chroma_key
        - aurora
      title: EffectType
      description: >-
        Values:

        - `vintage` — Faded, warm retro film look with lifted blacks.

        - `polaroid` — Instant-photo look — soft contrast, warm cast, subtle
        vignette.

        - `portra` — Kodak Portra-style film emulation with natural skin tones.

        - `super8` — Super-8 home-movie look — grain, gate weave, warm color.

        - `filmic` — Cinematic film grade with rolled-off highlights and rich
        contrast.

        - `add_grain` — Adds dusty analog film grain over the image.

        - `rgb_split` — Offsets the red/green/blue channels for a glitchy
        chromatic split.

        - `ghosting` — Trailing motion echoes, like persistence-of-vision
        smearing.

        - `displacement_map` — Warps the image with a displacement texture for a
        melted look.

        - `posterize` — Reduces the image to a small number of flat color bands.

        - `mosaic` — Pixelates the image into large mosaic blocks.

        - `mosaic_blur` — Pixelated mosaic with softened, blurred block edges.

        - `mosaic_posterize` — Combines mosaic pixelation with posterized
        colors.

        - `cc_halftone` — Comic-style halftone dot pattern.

        - `cc_halftone_blue` — Halftone dot pattern tinted blue.

        - `cc_halftone_green` — Halftone dot pattern tinted green.

        - `cc_halftone_red` — Halftone dot pattern tinted red.

        - `invert` — Inverts all colors (photo-negative).

        - `optics_compensation` — Fish-eye / lens-distortion warp.

        - `viewfinder` — Camera viewfinder overlay (REC indicator, framing
        marks).

        - `night_vision` — Green night-vision look with glow and noise.

        - `thermal` — Thermal-camera false-color heat map.

        - `camera_lens_blur` — Realistic bokeh lens blur across the frame.

        - `camera_lens_blur_bg` — Bokeh lens blur applied to the background
        only.

        - `box_blur` — Fast, uniform box blur.

        - `lens_flare` — Adds an anamorphic lens-flare streak and glints.

        - `strobe_light` — Rapid brightness strobing / flashing.

        - `snow` — Falling-snow particle overlay.

        - `glow` — Dreamy soft-glow bloom over highlights.

        - `sepia` — Sepia-toned monochrome with a vignette.

        - `echo` — Soft ghosted dream-echo overlay.

        - `chromatic_aberration` — Color-fringing at edges, like cheap-lens
        dispersion.

        - `vhs` — VHS tape look: tracking noise, color bleed, and wobble.

        - `vhs_overlay` — VHS noise/scanline layer composited over the source.

        - `crt` — CRT monitor look: screen curvature, scanlines, and phosphor
        glow.

        - `television` — Analog TV look: scanlines, static, and signal
        distortion.

        - `glitch` — Digital glitch: block displacement and channel tearing.

        - `compression_glitch` — MPEG compression artifacts: blocky macroblocks
        and datamosh.

        - `scanlines` — Horizontal scanlines overlaid on the image.

        - `prism` — Prismatic light refraction with a rainbow color split.

        - `light_leaks` — Warm light-leak streaks bleeding across the frame.

        - `film_burn` — Burning-film look with scorched edges and flares.

        - `duotone` — Two-tone color map (shadows to one hue, highlights to
        another).

        - `cross_process` — Cross-processed film look with shifted, punchy
        colors.

        - `bleach_bypass` — High-contrast, desaturated bleach-bypass film grade.

        - `chroma_key` — Keys out a color (green/blue screen); tune via
        `chroma_settings`.

        - `aurora` — Animated domain-warped fluid-gradient wash (§6); tune flow
        via `props` speed.
      x-enumDescriptions:
        vintage: Faded, warm retro film look with lifted blacks.
        polaroid: Instant-photo look — soft contrast, warm cast, subtle vignette.
        portra: Kodak Portra-style film emulation with natural skin tones.
        super8: Super-8 home-movie look — grain, gate weave, warm color.
        filmic: Cinematic film grade with rolled-off highlights and rich contrast.
        add_grain: Adds dusty analog film grain over the image.
        rgb_split: Offsets the red/green/blue channels for a glitchy chromatic split.
        ghosting: Trailing motion echoes, like persistence-of-vision smearing.
        displacement_map: Warps the image with a displacement texture for a melted look.
        posterize: Reduces the image to a small number of flat color bands.
        mosaic: Pixelates the image into large mosaic blocks.
        mosaic_blur: Pixelated mosaic with softened, blurred block edges.
        mosaic_posterize: Combines mosaic pixelation with posterized colors.
        cc_halftone: Comic-style halftone dot pattern.
        cc_halftone_blue: Halftone dot pattern tinted blue.
        cc_halftone_green: Halftone dot pattern tinted green.
        cc_halftone_red: Halftone dot pattern tinted red.
        invert: Inverts all colors (photo-negative).
        optics_compensation: Fish-eye / lens-distortion warp.
        viewfinder: Camera viewfinder overlay (REC indicator, framing marks).
        night_vision: Green night-vision look with glow and noise.
        thermal: Thermal-camera false-color heat map.
        camera_lens_blur: Realistic bokeh lens blur across the frame.
        camera_lens_blur_bg: Bokeh lens blur applied to the background only.
        box_blur: Fast, uniform box blur.
        lens_flare: Adds an anamorphic lens-flare streak and glints.
        strobe_light: Rapid brightness strobing / flashing.
        snow: Falling-snow particle overlay.
        glow: Dreamy soft-glow bloom over highlights.
        sepia: Sepia-toned monochrome with a vignette.
        echo: Soft ghosted dream-echo overlay.
        chromatic_aberration: Color-fringing at edges, like cheap-lens dispersion.
        vhs: 'VHS tape look: tracking noise, color bleed, and wobble.'
        vhs_overlay: VHS noise/scanline layer composited over the source.
        crt: 'CRT monitor look: screen curvature, scanlines, and phosphor glow.'
        television: 'Analog TV look: scanlines, static, and signal distortion.'
        glitch: 'Digital glitch: block displacement and channel tearing.'
        compression_glitch: 'MPEG compression artifacts: blocky macroblocks and datamosh.'
        scanlines: Horizontal scanlines overlaid on the image.
        prism: Prismatic light refraction with a rainbow color split.
        light_leaks: Warm light-leak streaks bleeding across the frame.
        film_burn: Burning-film look with scorched edges and flares.
        duotone: Two-tone color map (shadows to one hue, highlights to another).
        cross_process: Cross-processed film look with shifted, punchy colors.
        bleach_bypass: High-contrast, desaturated bleach-bypass film grade.
        chroma_key: Keys out a color (green/blue screen); tune via `chroma_settings`.
        aurora: >-
          Animated domain-warped fluid-gradient wash (§6); tune flow via `props`
          speed.
    IntensityKeyframe:
      properties:
        time:
          type: number
          minimum: 0
          title: Time
          description: Absolute timeline seconds.
        value:
          type: number
          maximum: 1
          minimum: 0
          title: Value
          description: Effect intensity 0–1 at this time.
      additionalProperties: false
      type: object
      required:
        - time
        - value
      title: IntensityKeyframe
      description: >-
        A point on a keyframed effect-intensity ramp (§2). Absolute timeline
        seconds.
    ChromaKeyProps:
      properties:
        key_color:
          type: string
          pattern: ^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$
          title: Key Color
          description: The colour to key out. Green-screen green by default.
          default: '#00FF00'
        threshold:
          type: number
          maximum: 1
          minimum: 0
          title: Threshold
          description: >-
            Chroma distance from `key_color` below which a pixel is fully
            transparent. Raise it if fringes of the screen survive.
          default: 0.1
        smoothness:
          type: number
          maximum: 1
          minimum: 0
          title: Smoothness
          description: >-
            Width of the ramp from transparent to opaque, starting at
            `threshold`. Raise it to soften hard edges; 0 gives a hard cut.
          default: 0.15
        spill_suppress:
          type: number
          maximum: 1
          minimum: 0
          title: Spill Suppress
          description: >-
            How strongly surviving pixels near the key colour are pulled toward
            their own luma, removing green spill on hair and edges. 0 disables.
          default: 0.5
      additionalProperties: false
      type: object
      title: ChromaKeyProps
      description: >-
        Used when ``EffectType.CHROMA_KEY`` is selected.


        Keys against an explicit key **colour** in the YCbCr chroma plane, which
        is

        luma-independent, so shadows falling on the screen key out along with
        it.


        This replaced an hue/saturation/luminance-window shape that could not
        work. It

        was wired to ``effect_chroma.frag``, whose ``sensitivity`` is

        ``(hueMax - hueMin) * 0.5`` compared against an Oklab distance bounded
        around

        0..0.9 — so the old defaults (60/180) produced a sensitivity of 60,
        discarded

        every pixel, and made the element vanish from the render. Nothing could
        have

        depended on the previous shape, because nothing rendered with it.
    MotionType:
      type: string
      enum:
        - identity
        - fade
        - slide_up
        - slide_down
        - slide_left
        - slide_right
        - zoom_in
        - zoom_out
        - rotate_cw
        - rotate_ccw
        - bounce
        - wipe_left
        - wipe_right
        - wipe_up
        - wipe_down
        - ken_burns_in
        - ken_burns_out
        - ken_burns_in_out
        - loop_wiggle
        - loop_rotate
        - loop_rotate_smooth
        - loop_3d_spin
        - loop_3d_sway
        - blur
        - evaporate
        - overlay
        - difference
        - rubber_in
        - whip_up
        - whip_down
        - glitch_pop
        - drift_in
        - drift_out
        - loop_breathe
        - loop_shimmer
        - swing_in
        - swing_out
        - elastic_rise
        - elastic_drop
        - tilt_zoom
        - loop_orbit
        - smooth_pop
      title: MotionType
      description: >-
        Values:

        - `identity` — Deliberate no-op carrier (no visible motion): attach a
        pure `audio` generator to any element without animating it. Element
        surfaces only.

        - `fade` — Fade in (entrance) or out (exit) via opacity.

        - `slide_up` — Slide in from below / out upward.

        - `slide_down` — Slide in from above / out downward.

        - `slide_left` — Slide in from the right / out to the left.

        - `slide_right` — Slide in from the left / out to the right.

        - `zoom_in` — Scale up from small to full size (entrance).

        - `zoom_out` — Scale down to small (exit).

        - `rotate_cw` — Spin in/out clockwise.

        - `rotate_ccw` — Spin in/out counter-clockwise.

        - `bounce` — Bounce in with an elastic overshoot / bounce out.

        - `wipe_left` — Reveal/conceal with a left-moving wipe.

        - `wipe_right` — Reveal/conceal with a right-moving wipe.

        - `wipe_up` — Reveal/conceal with an upward wipe.

        - `wipe_down` — Reveal/conceal with a downward wipe.

        - `ken_burns_in` — Slow zoom-in pan (Ken Burns) — entrance only.

        - `ken_burns_out` — Slow zoom-out pan (Ken Burns) — entrance only.

        - `ken_burns_in_out` — Slow zoom in then out (Ken Burns) — entrance
        only.

        - `loop_wiggle` — Continuous subtle wiggle (looping).

        - `loop_rotate` — Continuous stepped rotation (looping).

        - `loop_rotate_smooth` — Continuous smooth rotation (looping).

        - `loop_3d_spin` — Continuous 3D spin (looping).

        - `loop_3d_sway` — Continuous 3D sway (looping).

        - `blur` — Per-glyph soft blur-in (text entrance).

        - `evaporate` — Per-glyph rise, blur, and fade-out (text exit only).

        - `overlay` — Static Overlay blend fill (timing ignored).

        - `difference` — Static Difference blend fill (timing ignored).

        - `rubber_in` — Per-glyph rubbery overshoot as text springs in (text
        entrance).

        - `whip_up` — Per-glyph whip upward into place (text entrance).

        - `whip_down` — Per-glyph whip downward out of frame (text exit only).

        - `glitch_pop` — Per-glyph glitchy pop-in with jitter (text entrance).

        - `drift_in` — Per-glyph gentle drift and fade in (text entrance).

        - `drift_out` — Per-glyph gentle drift and fade out (text exit only).

        - `loop_breathe` — Per-glyph continuous breathing scale loop (text).

        - `loop_shimmer` — Per-glyph continuous shimmer loop (text).

        - `swing_in` — Element swings into place from an angle (entrance).

        - `swing_out` — Element swings away out of frame (exit only).

        - `elastic_rise` — Element rises with an elastic overshoot (entrance).

        - `elastic_drop` — Element drops away with an elastic recoil (exit
        only).

        - `tilt_zoom` — Element tilts and zooms into place (entrance).

        - `loop_orbit` — Element orbits continuously around its center (loop).

        - `smooth_pop` — Element pops in, scaling up as it fades in (entrance).
      x-enumDescriptions:
        identity: >-
          Deliberate no-op carrier (no visible motion): attach a pure `audio`
          generator to any element without animating it. Element surfaces only.
        fade: Fade in (entrance) or out (exit) via opacity.
        slide_up: Slide in from below / out upward.
        slide_down: Slide in from above / out downward.
        slide_left: Slide in from the right / out to the left.
        slide_right: Slide in from the left / out to the right.
        zoom_in: Scale up from small to full size (entrance).
        zoom_out: Scale down to small (exit).
        rotate_cw: Spin in/out clockwise.
        rotate_ccw: Spin in/out counter-clockwise.
        bounce: Bounce in with an elastic overshoot / bounce out.
        wipe_left: Reveal/conceal with a left-moving wipe.
        wipe_right: Reveal/conceal with a right-moving wipe.
        wipe_up: Reveal/conceal with an upward wipe.
        wipe_down: Reveal/conceal with a downward wipe.
        ken_burns_in: Slow zoom-in pan (Ken Burns) — entrance only.
        ken_burns_out: Slow zoom-out pan (Ken Burns) — entrance only.
        ken_burns_in_out: Slow zoom in then out (Ken Burns) — entrance only.
        loop_wiggle: Continuous subtle wiggle (looping).
        loop_rotate: Continuous stepped rotation (looping).
        loop_rotate_smooth: Continuous smooth rotation (looping).
        loop_3d_spin: Continuous 3D spin (looping).
        loop_3d_sway: Continuous 3D sway (looping).
        blur: Per-glyph soft blur-in (text entrance).
        evaporate: Per-glyph rise, blur, and fade-out (text exit only).
        overlay: Static Overlay blend fill (timing ignored).
        difference: Static Difference blend fill (timing ignored).
        rubber_in: Per-glyph rubbery overshoot as text springs in (text entrance).
        whip_up: Per-glyph whip upward into place (text entrance).
        whip_down: Per-glyph whip downward out of frame (text exit only).
        glitch_pop: Per-glyph glitchy pop-in with jitter (text entrance).
        drift_in: Per-glyph gentle drift and fade in (text entrance).
        drift_out: Per-glyph gentle drift and fade out (text exit only).
        loop_breathe: Per-glyph continuous breathing scale loop (text).
        loop_shimmer: Per-glyph continuous shimmer loop (text).
        swing_in: Element swings into place from an angle (entrance).
        swing_out: Element swings away out of frame (exit only).
        elastic_rise: Element rises with an elastic overshoot (entrance).
        elastic_drop: Element drops away with an elastic recoil (exit only).
        tilt_zoom: Element tilts and zooms into place (entrance).
        loop_orbit: Element orbits continuously around its center (loop).
        smooth_pop: Element pops in, scaling up as it fades in (entrance).
    Easing:
      type: string
      enum:
        - linear
        - hold
        - ease_in
        - ease_out
        - ease_in_out
        - sin_in
        - sin_out
        - sin_in_out
        - square_in
        - square_out
        - square_in_out
        - expo_in
        - expo_out
        - expo_in_out
        - quad_in
        - quad_out
        - quad_in_out
        - cubic_in
        - cubic_out
        - cubic_in_out
        - quart_in
        - quart_out
        - quart_in_out
        - quint_in
        - quint_out
        - quint_in_out
      title: Easing
      description: >-
        The engine's shared easing name map (Easing.cpp `StringToEasingType`).


        Every name here resolves on every keyframe surface
        (morph/trim/mask/custom

        animation keyframes, selector ``ease``, counter ``easing``). ``ease_in``
        /

        ``ease_out`` / ``ease_in_out`` are the AE/CSS aliases of the ``quad_*``

        curves. ``square_*`` is a **step** family (``square_in_out`` snaps at
        the

        midpoint), not a power curve — the quadratic curves are ``quad_*``.

        ``hold`` freezes the prior value until the next keyframe.


        The parameterized families (spring / back / elastic / bezier) are not

        names: express them through the dedicated keyframe sub-objects.


        Values:

        - `linear` — Constant speed, no acceleration.

        - `hold` — Freeze the previous value until the next keyframe (step).

        - `ease_in` — Starts slow, accelerates (alias of quad_in).

        - `ease_out` — Starts fast, decelerates (alias of quad_out).

        - `ease_in_out` — Slow start and end, faster in the middle (alias of
        quad_in_out).

        - `sin_in` — Gentle sinusoidal acceleration in.

        - `sin_out` — Gentle sinusoidal deceleration out.

        - `sin_in_out` — Gentle sinusoidal ease both ends.

        - `square_in` — Step: snaps at the segment end (not a power curve).

        - `square_out` — Step: snaps at the segment start (not a power curve).

        - `square_in_out` — Step: snaps at the midpoint — the typewriter/glitch
        snap.

        - `expo_in` — Extreme exponential acceleration in.

        - `expo_out` — Extreme exponential deceleration out.

        - `expo_in_out` — Extreme exponential ease both ends.

        - `quad_in` — Quadratic acceleration in.

        - `quad_out` — Quadratic deceleration out.

        - `quad_in_out` — Quadratic ease both ends.

        - `cubic_in` — Cubic acceleration in (stronger than quad).

        - `cubic_out` — Cubic deceleration out (stronger than quad).

        - `cubic_in_out` — Cubic ease both ends.

        - `quart_in` — Quartic acceleration in.

        - `quart_out` — Quartic deceleration out.

        - `quart_in_out` — Quartic ease both ends.

        - `quint_in` — Quintic acceleration in (near-expo).

        - `quint_out` — Quintic deceleration out (near-expo).

        - `quint_in_out` — Quintic ease both ends.
      x-enumDescriptions:
        linear: Constant speed, no acceleration.
        hold: Freeze the previous value until the next keyframe (step).
        ease_in: Starts slow, accelerates (alias of quad_in).
        ease_out: Starts fast, decelerates (alias of quad_out).
        ease_in_out: Slow start and end, faster in the middle (alias of quad_in_out).
        sin_in: Gentle sinusoidal acceleration in.
        sin_out: Gentle sinusoidal deceleration out.
        sin_in_out: Gentle sinusoidal ease both ends.
        square_in: 'Step: snaps at the segment end (not a power curve).'
        square_out: 'Step: snaps at the segment start (not a power curve).'
        square_in_out: 'Step: snaps at the midpoint — the typewriter/glitch snap.'
        expo_in: Extreme exponential acceleration in.
        expo_out: Extreme exponential deceleration out.
        expo_in_out: Extreme exponential ease both ends.
        quad_in: Quadratic acceleration in.
        quad_out: Quadratic deceleration out.
        quad_in_out: Quadratic ease both ends.
        cubic_in: Cubic acceleration in (stronger than quad).
        cubic_out: Cubic deceleration out (stronger than quad).
        cubic_in_out: Cubic ease both ends.
        quart_in: Quartic acceleration in.
        quart_out: Quartic deceleration out.
        quart_in_out: Quartic ease both ends.
        quint_in: Quintic acceleration in (near-expo).
        quint_out: Quintic deceleration out (near-expo).
        quint_in_out: Quintic ease both ends.
    MotionScope:
      type: string
      enum:
        - element
        - character
      title: MotionScope
      description: >-
        Whether a motion preset applies to the whole element or per-glyph (text
        only).


        Values:

        - `element` — The motion animates the whole element as one unit.

        - `character` — The motion animates each text glyph individually (text
        only).
      x-enumDescriptions:
        element: The motion animates the whole element as one unit.
        character: The motion animates each text glyph individually (text only).
    AudioGenerator:
      properties:
        feature:
          type: string
          enum:
            - amplitude
            - bass
            - mid
            - high
            - beat
          title: Feature
          default: amplitude
        scale:
          type: number
          title: Scale
          description: Scale offset per unit feature.
          default: 0
        opacity:
          type: number
          title: Opacity
          description: Opacity offset per unit feature.
          default: 0
        translate:
          anyOf:
            - $ref: '#/components/schemas/Point'
            - type: 'null'
          description: Translate offset per unit feature.
        rotate:
          type: number
          title: Rotate
          description: Rotation offset (degrees) per unit feature.
          default: 0
      additionalProperties: false
      type: object
      title: AudioGenerator
      description: >-
        Audio-reactive property offsets driven by the soundtrack FFT (§3, W2).


        Each frame the mix reduces to the chosen feature and the element's
        properties

        get offset by ``amount × feature`` — beat-bounce, bass-pumped scale,
        etc.
    WordAnimationStyle:
      type: string
      enum:
        - glow
        - box
        - scale_pop
        - slide_up
        - fly_in
        - color
      title: WordAnimationStyle
      description: |-
        [API extension] Word-level animations (require word_animation.words).

        Values:
        - `glow` — Highlights the active word with a glowing emphasis.
        - `box` — Draws a colored box behind the active word.
        - `scale_pop` — Pops the active word with a quick scale punch.
        - `slide_up` — Floats each word up into place as it becomes active.
        - `fly_in` — Drops each word in from above as it becomes active.
        - `color` — Recolors the active word (karaoke-style highlight).
      x-enumDescriptions:
        glow: Highlights the active word with a glowing emphasis.
        box: Draws a colored box behind the active word.
        scale_pop: Pops the active word with a quick scale punch.
        slide_up: Floats each word up into place as it becomes active.
        fly_in: Drops each word in from above as it becomes active.
        color: Recolors the active word (karaoke-style highlight).
    Word:
      properties:
        text:
          type: string
          title: Text
        start:
          type: number
          minimum: 0
          title: Start
          description: ABSOLUTE output-timeline seconds (not element-relative).
        end:
          type: number
          exclusiveMinimum: 0
          title: End
          description: ABSOLUTE output-timeline seconds (not element-relative).
      additionalProperties: false
      type: object
      required:
        - text
        - start
        - end
      title: Word
    WordStyle:
      properties:
        color:
          anyOf:
            - type: string
              pattern: ^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$
            - type: 'null'
          title: Color
        bold:
          anyOf:
            - type: boolean
            - type: 'null'
          title: Bold
        italic:
          anyOf:
            - type: boolean
            - type: 'null'
          title: Italic
        underline:
          anyOf:
            - type: boolean
            - type: 'null'
          title: Underline
      additionalProperties: false
      type: object
      title: WordStyle
      description: Per-word style override (§4 W3). Whole-word matches only.
    AnimationKeyframe:
      properties:
        time:
          type: number
          maximum: 1
          minimum: 0
          title: Time
          description: Fraction of the animation (0–1).
        easing:
          anyOf:
            - type: string
            - type: 'null'
          title: Easing
          description: >-
            Easing into this keyframe — any `easings` catalog name (`quad_out`,
            `expo_in_out`, `hold`, …). Unknown names are rejected. A
            `bezier`/`spring`/`back`/`elastic` sub-object overrides it.
        bezier:
          anyOf:
            - $ref: '#/components/schemas/BezierEasing'
            - type: 'null'
        spring:
          anyOf:
            - $ref: '#/components/schemas/SpringEasing'
            - type: 'null'
        back:
          anyOf:
            - $ref: '#/components/schemas/BackEasing'
            - type: 'null'
        elastic:
          anyOf:
            - $ref: '#/components/schemas/ElasticEasing'
            - type: 'null'
        translate:
          anyOf:
            - $ref: '#/components/schemas/Point'
            - type: 'null'
        path:
          anyOf:
            - $ref: '#/components/schemas/SpatialPath'
            - type: 'null'
          description: >-
            Curve the translate into this keyframe along a cubic bezier
            (`c1`/`c2` control points).
        scale:
          anyOf:
            - type: number
            - $ref: '#/components/schemas/ScaleXY'
            - type: 'null'
          title: Scale
          description: Uniform scale factor, or `{x, y}` for non-uniform scale.
        rotate:
          anyOf:
            - type: number
            - type: 'null'
          title: Rotate
          description: In-plane (z) rotation, degrees.
        rotation:
          anyOf:
            - $ref: '#/components/schemas/RotationXYZ'
            - type: 'null'
          description: >-
            Full 3D rotation `{x, y, z}` (degrees); overrides `rotate`. Non-zero
            x/y = animated perspective tilt.
        opacity:
          anyOf:
            - type: number
            - type: 'null'
          title: Opacity
        color:
          anyOf:
            - type: string
              pattern: ^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$
            - type: 'null'
          title: Color
          description: >-
            Colour tint at this keyframe (multiplied with the element). An
            8-digit hex alpha composes with `opacity` — when both are set,
            `opacity` wins for the alpha channel.
        blur:
          anyOf:
            - type: number
              minimum: 0
            - type: 'null'
          title: Blur
          description: Per-glyph blur amount (fraction of the em). Text registries only.
      additionalProperties: false
      type: object
      required:
        - time
      title: AnimationKeyframe
      description: >-
        A keyframe in a custom animation definition (§3). Time is 0–1 of the
        animation.


        Property channels mirror the engine's catalog file format, so anything a

        shipped preset expresses is expressible here: position (`translate`,
        curved

        via `path`), uniform or per-axis `scale`, scalar z `rotate` or full 3D

        `rotation`, `opacity`/`color` tint, per-glyph `blur` (text registries
        only),

        and the full easing vocabulary including the parameterized families

        (`bezier` / `spring` / `back` / `elastic`).
    RangeSelector:
      properties:
        based_on:
          type: string
          enum:
            - glyph
            - word
          title: Based On
          default: glyph
        start:
          type: number
          maximum: 1
          minimum: 0
          title: Start
          description: Range start, fraction along the text.
          default: 0
        end:
          type: number
          maximum: 1
          minimum: 0
          title: End
          description: Range end, fraction along the text.
          default: 1
        falloff:
          type: number
          minimum: 0
          title: Falloff
          description: Ease to zero over this fraction beyond each edge.
          default: 0
        ease:
          anyOf:
            - type: string
            - type: 'null'
          title: Ease
          description: Easing curve name for the falloff.
        invert:
          type: boolean
          title: Invert
          default: false
      additionalProperties: false
      type: object
      title: RangeSelector
      description: >-
        AE-style range selector: shape a text animation's amount by position
        (§4, W2).
    SpatialPath:
      properties:
        c1:
          $ref: '#/components/schemas/Point'
        c2:
          $ref: '#/components/schemas/Point'
      additionalProperties: false
      type: object
      required:
        - c1
        - c2
      title: SpatialPath
      description: >-
        Cubic-bezier control points curving the translate into this keyframe.


        The segment from the previous keyframe's position travels along the
        bezier

        defined by `c1`/`c2` (same units as `translate`) instead of a straight
        line.
    ScaleXY:
      properties:
        x:
          type: number
          title: X
          default: 1
        'y':
          type: number
          title: 'Y'
          default: 1
      additionalProperties: false
      type: object
      title: ScaleXY
      description: Non-uniform scale for a custom-animation keyframe.
    RotationXYZ:
      properties:
        x:
          type: number
          title: X
          default: 0
        'y':
          type: number
          title: 'Y'
          default: 0
        z:
          type: number
          title: Z
          default: 0
      additionalProperties: false
      type: object
      title: RotationXYZ
      description: >-
        3D rotation (degrees) for a custom-animation keyframe.


        Non-zero `x`/`y` renders true perspective tilt through the engine's 45°
        fov

        camera — the animated 3D-card family (spins, tilting reveals). This is
        also

        the engine-recommended way to tilt *text*, which has no static tilt.
  securitySchemes:
    ApiKey:
      type: http
      scheme: bearer

````