{"openapi":"3.1.0","info":{"title":"Framelane API","description":"Framelane renders video and runs AI media tasks from a single declarative request.\n\n### Getting access (self-serve, no human needed)\nYou can provision your own workspace and API key end-to-end — no dashboard, no sales call:\n1. `POST /v1/signup` with a `workspace_name` and an `email` you control. You get an API key\n   back immediately and a 6-digit code is emailed to that address.\n2. Read the code from that inbox and `POST /v1/signup/verify` with it. If you drive a\n   programmatic inbox (e.g. an agent mail service), this whole loop runs unattended.\n3. Your key works the moment the email is verified. Until then every authed call returns\n   `403 email_not_verified` — verification is the only gate, so an unverified workspace can\n   never spend. Re-calling `POST /v1/signup` re-keys the workspace and requires re-verifying.\n\n`POST /v1/signup`, `POST /v1/signup/verify`, and `GET /v1/capabilities` need **no** API key.\n\n### Authentication\nSend your API key as a bearer token: `Authorization: Bearer fl_live_...`.\n\n### Core workflow\n1. **Get media in.** Pass any publicly accessible URL directly, or `POST /v1/uploads` to\n   get a signed URL, then `PUT` your file to it.\n2. **Submit one JSON payload.** `POST /v1/renders` composes a whole scene (canvas +\n   `elements[]` + `transitions[]`) in a single request; `POST /v1/tasks/{type}` runs an AI\n   operation (transcribe).\n3. **Wait for completion.** Jobs are async: poll `GET /v1/{renders|tasks}/{id}` or register\n   a webhook. Statuses end in `completed`, `failed`, or `cancelled`.\n4. **Fetch the result.** A render's artifact is a file: use `output.url` or\n   `GET /v1/renders/{id}/download` for a short-lived signed URL. A task that returns data\n   puts it on the job's `result` object — `transcribe` returns its transcript there, has no\n   `output`, and `404`s on `/v1/tasks/{id}/download`.\n\n### Discovering what's possible\nCall **`GET /v1/capabilities`** (no auth) for the machine-readable catalog of every effect,\nmotion, transition, format, element type, and task parameter — each flagged with whether the\nrenderer supports it, plus all numeric ranges and rate limits. Validate against it before\nsubmitting to avoid `422`s.\n\n### Idempotency\n`POST` endpoints accept an `Idempotency-Key` header. The same key with the same body replays\nthe original response (`200`); with a different body it returns `409 Conflict`.\n\n### Errors\nEvery error has the shape `{\"error\": {\"code\", \"message\", \"details\"}}`. The machine-readable\n`code` (e.g. `source_not_found`, `quota_exceeded`, `codec_unsupported`) is stable — branch on\nit to self-correct.\n","version":"0.2.0"},"servers":[{"url":"https://api.framelane.io","description":"Production"}],"paths":{"/v1/health":{"get":{"tags":["system"],"summary":"Health","operationId":"health","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}}}},"/v1/ready":{"get":{"tags":["system"],"summary":"Ready","operationId":"ready","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadinessResponse"}}}},"503":{"description":"Service Unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadinessResponse"}}}}}}},"/v1/version":{"get":{"tags":["system"],"summary":"Version","operationId":"version","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VersionResponse"}}}}}}},"/v1/capabilities":{"get":{"tags":["capabilities"],"summary":"List render and task capabilities","description":"Return the catalog of everything an agent can use in a render or task —\neffects, motions, transitions, output/input formats, element types, task\nparameters, and global limits — each flagged with whether it is supported today.","operationId":"get_capabilities","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CapabilitiesResponse"}}}}}}},"/v1/skills":{"get":{"tags":["skills"],"summary":"Skills Manifest","operationId":"skills_manifest","parameters":[{"name":"X-Run-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Run-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Skills Manifest V1 Skills Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/skills/{name}":{"get":{"tags":["skills"],"summary":"Skill Markdown","operationId":"skill_markdown","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"X-Run-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Run-Id"}}],"responses":{"200":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/signup":{"post":{"tags":["signup"],"summary":"Create a workspace and send an OTP verification email","description":"Creates a new workspace and API key, then sends a 6-digit OTP to the supplied email address. Calling this endpoint again with the same email rotates the API key and re-sends the OTP (idempotent). The rotated key is gated until you re-verify with the new OTP, so knowing an email alone never yields a working key. Call `POST /v1/signup/verify` with the code to verify.","operationId":"signup","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/signup/verify":{"post":{"tags":["signup"],"summary":"Verify email with OTP code","description":"Submit the 6-digit OTP sent to the email supplied during signup. On success the workspace is marked as email-verified and the OTP is consumed. After 5 wrong guesses the code is invalidated and a new signup call is required to obtain a fresh one.","operationId":"verify_signup","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupVerifyRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceOut"}}}},"400":{"description":"Invalid or expired OTP code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No workspace found for this email.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/auth/sync":{"post":{"tags":["auth"],"summary":"Sync a Clerk session to a Framelane workspace","description":"Verify a Clerk session token and create, adopt or return the workspace for that Clerk user. On first login a Workspace and initial API key are created and the plaintext key is returned once. If an email-signup workspace already exists for the verified Clerk primary email it is adopted instead, keeping its projects, billing and verified keys. Subsequent logins return the workspace without exposing a key.","operationId":"auth_sync","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthSyncRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthSyncResponse"}}}},"401":{"description":"Invalid or expired Clerk token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The Clerk primary email is not verified.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Account setup temporarily unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/workspace":{"get":{"tags":["workspace"],"summary":"Get workspace","description":"Returns the workspace associated with the authenticated API key.","operationId":"get_workspace","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceOut"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"ApiKey":[]}]},"delete":{"tags":["workspace"],"summary":"Delete workspace","description":"Permanently delete the workspace and ALL associated data (API keys, render jobs, tasks, webhooks). This action is irreversible.","operationId":"delete_workspace","responses":{"204":{"description":"Successful Response"},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"ApiKey":[]}]},"patch":{"tags":["workspace"],"summary":"Update workspace","description":"Update the workspace display name.","operationId":"update_workspace","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceUpdate"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceOut"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"ApiKey":[]}]}},"/v1/workspace/usage":{"get":{"tags":["workspace"],"summary":"Get workspace usage","description":"Returns render minutes, render count, task count, and egress bytes for the requested period. Pass `period` as `7d`, `30d`, or `90d` for a rolling window ending now; omit it for the current calendar-month billing period (the window the FREE-plan render-minute cap also resets on).","operationId":"get_usage","security":[{"ApiKey":[]}],"parameters":[{"name":"period","in":"query","required":false,"schema":{"anyOf":[{"enum":["7d","30d","90d"],"type":"string"},{"type":"null"}],"description":"Rolling window ending now. Omit for the current calendar-month billing period.","title":"Period"},"description":"Rolling window ending now. Omit for the current calendar-month billing period."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceUsage"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/workspace/assets":{"get":{"tags":["workspace"],"summary":"List workspace media assets","description":"Returns a cursor-paginated list of the workspace's uploaded video, audio and image assets that are ready for use. Optionally filter by `kind` (`video`, `audio` or `image`).\n\nVideo and audio appear once their duration is known; an image has no duration to extract and is listed as soon as it is registered.","operationId":"list_assets","security":[{"ApiKey":[]}],"parameters":[{"name":"kind","in":"query","required":false,"schema":{"anyOf":[{"enum":["video","audio","image"],"type":"string"},{"type":"null"}],"title":"Kind"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CursorPage_MediaAssetOut_"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/workspace/assets/{asset_id}":{"get":{"tags":["workspace"],"summary":"Get one media asset","description":"Returns a single asset, **including one the list does not show yet**.\n\nThis is how you confirm an upload landed. `POST /v1/uploads` mints the asset row when it reserves the slot, and the bytes then travel from your client straight to storage without passing through this API — so a `201` there proves a slot exists, never that a file arrived. `bytes_uploaded` answers that by checking storage; `false` means the transfer never completed and the row is a reservation you should re-upload or `DELETE`.","operationId":"get_asset","security":[{"ApiKey":[]}],"parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","title":"Asset Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MediaAssetDetailOut"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Asset not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"delete":{"tags":["workspace"],"summary":"Delete a media asset","description":"Remove an asset from the workspace and delete its stored file.\n\nUse this to clear an upload that never completed: the asset row is created when the upload URL is reserved, so a transfer that failed leaves a listed asset whose bytes are missing, and every render referencing it fails to download. Deleting an asset does NOT rewrite stored projects or past renders that name its URL; those will fail until the element is repointed.","operationId":"delete_asset","security":[{"ApiKey":[]}],"parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","title":"Asset Id"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Asset not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/api-keys":{"post":{"tags":["api-keys"],"summary":"Create API key","description":"Create a new API key for the workspace. The plaintext key is returned once.","operationId":"create_api_key","security":[{"ApiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreated"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"get":{"tags":["api-keys"],"summary":"List API keys","description":"Returns all API keys for the workspace, including revoked ones.","operationId":"list_api_keys","security":[{"ApiKey":[]}],"parameters":[{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CursorPage_ApiKeyOut_"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/api-keys/{key_id}":{"delete":{"tags":["api-keys"],"summary":"Revoke API key","description":"Revoke an API key. Revoked keys are rejected on all subsequent requests.","operationId":"revoke_api_key","security":[{"ApiKey":[]}],"parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"API key not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/renders":{"post":{"tags":["renders"],"summary":"Submit a render job","description":"Enqueue a new render job on the GPU engine. The job moves through queued → processing → completed / failed asynchronously. Attach a webhook URL or register workspace-level webhooks to receive status updates. The response carries an ADVISORY `ok` + `violations[]` lint of the composition: the render is dispatched either way, and anything that actually blocks a submit is a 422 instead.","operationId":"create_render","security":[{"ApiKey":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenderRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenderSubmitOut"}}}},"200":{"description":"Idempotent replay — same Idempotency-Key reused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenderSubmitOut"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"Idempotency conflict (different body for the same key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Free-tier render quota exceeded (upgrade to continue).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation error or unsupported feature.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"get":{"tags":["renders"],"summary":"List renders","description":"Returns a cursor-paginated list of render jobs for the authenticated workspace.","operationId":"list_renders","security":[{"ApiKey":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/JobStatus"},{"type":"null"}],"title":"Status"}},{"name":"project_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Only return renders submitted for this project (POST /v1/projects/{id}/renders).","title":"Project Id"},"description":"Only return renders submitted for this project (POST /v1/projects/{id}/renders)."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":20,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CursorPage_JobOut_"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/renders/{render_id}":{"get":{"tags":["renders"],"summary":"Get a render","description":"Fetch a render's status. Pass `wait` (seconds) to long-poll: the request holds until the render reaches a terminal status (`completed`/`failed`/`cancelled`) or the hold elapses, then returns the current status — one call instead of a poll loop, which is the agent-first way to wait (also covers preview renders). When it returns still in-flight, `Retry-After: 0` is set; re-call to keep waiting.","operationId":"get_render","security":[{"ApiKey":[]}],"parameters":[{"name":"render_id","in":"path","required":true,"schema":{"type":"string","title":"Render Id"}},{"name":"wait","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Seconds to block until the render is terminal (capped server-side; values above the cap are clamped). 0 (default) returns immediately.","default":0,"title":"Wait"},"description":"Seconds to block until the render is terminal (capped server-side; values above the cap are clamped). 0 (default) returns immediately."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobOut"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Render not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"delete":{"tags":["renders"],"summary":"Cancel a render","description":"Cancel a render job. Best-effort — a render already picked up by a worker may finish before the cancel signal is observed. A no-op if the render has already reached a terminal status (`completed`, `failed`, or `cancelled`).","operationId":"cancel_render","security":[{"ApiKey":[]}],"parameters":[{"name":"render_id","in":"path","required":true,"schema":{"type":"string","title":"Render Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobOut"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Render not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/renders/{render_id}/download":{"get":{"tags":["renders"],"summary":"Download render artifact","description":"Redirects to a short-lived signed GCS URL for the completed render artifact.","operationId":"download_render","security":[{"ApiKey":[]}],"parameters":[{"name":"render_id","in":"path","required":true,"schema":{"type":"string","title":"Render Id"}}],"responses":{"302":{"description":"Redirect to a 5-minute signed URL."},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Render not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Render not yet completed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/renders/{render_id}/poster":{"get":{"tags":["renders"],"summary":"Get render poster image","description":"Redirects to a short-lived signed URL for a completed render's poster IMAGE — e.g. a contact-sheet preview PNG. This is the pixels-you-can-embed counterpart to the MCP `get_render_preview` tool. Returns 422 if the render output is a video; use `/download` for that.","operationId":"render_poster","security":[{"ApiKey":[]}],"parameters":[{"name":"render_id","in":"path","required":true,"schema":{"type":"string","title":"Render Id"}}],"responses":{"302":{"description":"Redirect to a 5-minute signed URL for the poster image."},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Render not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Render not completed, or its output is a video (no poster).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/preview":{"post":{"tags":["preview"],"summary":"Validate or preview a composition","description":"Validate a RenderRequest cheaply, or preview it at a single frame or over a short window without a full render. Pass `dry_run: true` for the free linter (cross-element and whole-timeline invariants, no render); it also returns the translated render `task`. A pixel preview reuses the exact render pipeline restricted to an outputSlice, so it is faithful to the final export; the returned job is a preview (not billed) that you poll via GET /v1/renders/{id}.","operationId":"create_preview","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreviewRequest"}}},"required":true},"responses":{"200":{"description":"Validation result (dry-run).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreviewResponse"}}}},"202":{"description":"Preview render queued; poll job via GET /v1/renders/{id}.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreviewResponse"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Malformed request or unsupported source.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"ApiKey":[]}]}},"/v1/projects":{"post":{"tags":["projects"],"summary":"Create a project","description":"Create a stateful editing project from a RenderRequest, or empty. Elements without an id are assigned one so ops can address them. Returns the full project at version 1, plus an ADVISORY `ok` + `violations[]` lint of the seed composition — a project is allowed to hold an invalid head while you repair it with ops, so findings never block the 201.\n\nPass `source_project_id` instead of a request to copy a project: your own project or template, or a published template from `GET /v1/templates`. That one call duplicates a project, saves one as a template (`kind: template`) and starts a project from a template; `source_kind` records which it was.","operationId":"create_project","security":[{"ApiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectCreated"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"plan_required: custom templates need a higher plan (only when the gate is on).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"source_project_id is not a project you can read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Malformed render request, or both render_request and source_project_id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"get":{"tags":["projects"],"summary":"List projects","description":"Cursor-paginated projects for the workspace. Each item is a compact summary. `kind` defaults to `project`, so templates are listed only with `kind=template` or `kind=all`.","operationId":"list_projects","security":[{"ApiKey":[]}],"parameters":[{"name":"kind","in":"query","required":false,"schema":{"enum":["project","template","all"],"type":"string","default":"project","title":"Kind"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":20,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CursorPage_ProjectOut_"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/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. A published template from another workspace reads here too, read-only: every write route answers 404 for it.","operationId":"get_project","security":[{"ApiKey":[]}],"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"}}}}}},"patch":{"tags":["projects"],"summary":"Rename a project","description":"Rename a project or template. Bumps `updated_at`; the head and its version are unchanged.","operationId":"rename_project","security":[{"ApiKey":[]}],"parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectUpdate"}}}},"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"}}}}}},"delete":{"tags":["projects"],"summary":"Delete a project","description":"Soft-delete a project. It stops appearing in list/get calls, but its op history and any renders submitted from it are kept.","operationId":"delete_project","security":[{"ApiKey":[]}],"parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","title":"Project Id"}}],"responses":{"204":{"description":"Successful Response"},"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"}}}}}}},"/v1/projects/{project_id}/ops":{"post":{"tags":["projects"],"summary":"Apply targeted edits","description":"Apply an atomic batch of ops to the project head. Returns the new version, the ids touched, a compact diff, and validation that rides along for free. Pass `if_version` for optimistic concurrency (409 on mismatch). `apply_template` replaces the composition with a template's and records the template as the project's source.","operationId":"apply_project_ops","security":[{"ApiKey":[]}],"parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplyOpsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplyOpsResult"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Project not found, or an apply_template names a template you cannot read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"plan_required: custom templates need a higher plan (only when the gate is on).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"if_version did not match the current head version.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"A structurally impossible op (unknown id, invalid patch).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/projects/{project_id}/preview":{"post":{"tags":["projects"],"summary":"Preview or validate the project head","description":"Validate the project head cheaply, or preview it at a frame or window without a full render. Same free linter and faithful sliced preview as POST /v1/preview, against the stored head instead of an inline request.","operationId":"preview_project","security":[{"ApiKey":[]}],"parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreviewOptions"}}}},"responses":{"200":{"description":"Validation result (dry-run).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreviewResponse"}}}},"202":{"description":"Preview render queued; poll via GET /v1/renders/{id}.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreviewResponse"}}}},"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":"Unsupported source.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/projects/{project_id}/thumbnail":{"post":{"tags":["projects"],"summary":"Refresh the project thumbnail","description":"Queue a poster frame of the head: a single-frame preview (not billed) at the first moment the contact-sheet planner picks after zero. When it completes, the project's `thumbnail_url` and `thumbnail_version` update; the thumbnail is stale while `thumbnail_version` is below `version`. A call while one is in flight for the same version returns that job instead of queueing another. Saving a template and publishing one ask for a thumbnail on their own.","operationId":"refresh_project_thumbnail","security":[{"ApiKey":[]}],"parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","title":"Project Id"}}],"responses":{"202":{"description":"Thumbnail queued, or the one already in flight for this version.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ThumbnailQueued"}}}},"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":"The head cannot be previewed yet: a source is missing or still processing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/projects/{project_id}/renders":{"post":{"tags":["projects"],"summary":"Render the project head","description":"Submit the current project head to the render engine as a standard (billable) render. Idempotency-Key note: reusing a key returns the original job unconditionally -- the body (here, the project head at submission time) is not compared, so a key reused after the project changed still replays the earlier job rather than 409ing. The response carries an ADVISORY `ok` + `violations[]` lint of the head; the render is dispatched either way.","operationId":"render_project","security":[{"ApiKey":[]}],"parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","title":"Project Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenderSubmitOut"}}}},"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 or unsupported feature.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/templates":{"get":{"tags":["templates"],"summary":"List templates","description":"The template gallery, newest first. No API key needed: without one the list is the public catalogue. With one, `scope` defaults to `all`, adding your own templates (`scope: workspace` on each card). Filter with `category`, `q` (name, title or description contains, case-insensitive), `aspect` and `family` (the size variants of one design). Public-only pages carry `Cache-Control: public, max-age=300` and an `ETag`; send `If-None-Match` for a 304.","operationId":"list_templates","security":[{"ApiKey":[]}],"parameters":[{"name":"scope","in":"query","required":false,"schema":{"anyOf":[{"enum":["public","workspace","all"],"type":"string"},{"type":"null"}],"description":"Defaults to `all` with an API key, `public` without one.","title":"Scope"},"description":"Defaults to `all` with an API key, `public` without one."},{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Q"}},{"name":"aspect","in":"query","required":false,"schema":{"anyOf":[{"enum":["landscape","portrait","square"],"type":"string"},{"type":"null"}],"title":"Aspect"}},{"name":"family","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Family"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":20,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CursorPage_TemplateOut_"}}}},"401":{"description":"Invalid API key, or scope=workspace|all without one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"post":{"tags":["templates"],"summary":"Publish a template","description":"Admin workspaces only. List one of your template projects in the gallery. `status: published` makes it public, which needs every media URL in its composition on the shared CDN (`template_assets_not_shared` lists the rest) and no `webhook_url` (`template_has_webhook`), since every copy inherits it; a `draft` is visible to admin workspaces only.","operationId":"publish_template","security":[{"ApiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateListingCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateListingOut"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"admin_only: not an admin workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"project_id is not a project in your workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"The slug is taken, or the project already has a listing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Not a template, or template_assets_not_shared or template_has_webhook when publishing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/templates/{id_or_slug}":{"get":{"tags":["templates"],"summary":"Get a template","description":"One template by project id or listing slug, with its `render_request`, so it can be inspected before it is used. No API key needed for a published template.","operationId":"get_template","security":[{"ApiKey":[]}],"parameters":[{"name":"id_or_slug","in":"path","required":true,"schema":{"type":"string","title":"Id Or Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateDetail"}}}},"401":{"description":"Invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No template you can read has this id or slug.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/templates/{listing_id}":{"patch":{"tags":["templates"],"summary":"Update a template listing","description":"Admin workspaces only. Send only the fields that change; null clears an optional one. A listing that ends up published is checked like a new one.","operationId":"update_template_listing","security":[{"ApiKey":[]}],"parameters":[{"name":"listing_id","in":"path","required":true,"schema":{"type":"string","title":"Listing Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateListingUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateListingOut"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"admin_only: not an admin workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Listing not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"The slug is taken.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"template_assets_not_shared or template_has_webhook when the result is published.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"delete":{"tags":["templates"],"summary":"Unpublish a template","description":"Admin workspaces only. Deletes the listing; the template project stays.","operationId":"unpublish_template","security":[{"ApiKey":[]}],"parameters":[{"name":"listing_id","in":"path","required":true,"schema":{"type":"string","title":"Listing Id"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"admin_only: not an admin workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Listing not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/brand-kits":{"post":{"tags":["brand-kits"],"summary":"Extract a brand kit from a URL","description":"Fetch a public website and extract its palette, fonts, logo, and corner feel (best-effort, with per-field confidence). Override anything with PATCH.","operationId":"create_brand_kit","security":[{"ApiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandKitExtractRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandKitOut"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"The URL could not be fetched or parsed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"get":{"tags":["brand-kits"],"summary":"List brand kits","description":"Returns a cursor-paginated list of brand kits for the workspace.","operationId":"list_brand_kits","security":[{"ApiKey":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":20,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CursorPage_BrandKitOut_"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/brand-kits/{brand_kit_id}":{"get":{"tags":["brand-kits"],"summary":"Get a brand kit","description":"Fetch a single brand kit's extracted palette, fonts, logo, and corner feel.","operationId":"get_brand_kit","security":[{"ApiKey":[]}],"parameters":[{"name":"brand_kit_id","in":"path","required":true,"schema":{"type":"string","title":"Brand Kit Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandKitOut"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Brand kit not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"patch":{"tags":["brand-kits"],"summary":"Override brand kit fields","description":"Manually correct any extracted field; provided palette/fonts keys merge in.","operationId":"update_brand_kit","security":[{"ApiKey":[]}],"parameters":[{"name":"brand_kit_id","in":"path","required":true,"schema":{"type":"string","title":"Brand Kit Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandKitUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandKitOut"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Brand kit not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"delete":{"tags":["brand-kits"],"summary":"Delete a brand kit","description":"Permanently remove a brand kit. Renders that already reference it are unaffected.","operationId":"delete_brand_kit","security":[{"ApiKey":[]}],"parameters":[{"name":"brand_kit_id","in":"path","required":true,"schema":{"type":"string","title":"Brand Kit Id"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Brand kit not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/shaders":{"post":{"tags":["shaders"],"summary":"Compile and store a shader","description":"Compile an SkSL source into the GLSL and WGSL variants the render engines run, and store it. Use the returned `id` as `effects[].shader.id` on a video, image, shape, frame or instance; `POST /v1/shaders/{id}/probe` renders one frame of it first.","operationId":"create_shader","security":[{"ApiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShaderCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShaderOut"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"`shader_compile_error`: the source did not compile (`details.errors` lists each error with its line and column; the failed shader is stored as `details.shader_id`), or `source_too_large`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"`shader_compiler_unavailable`: no compiler on this deployment, or it timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"get":{"tags":["shaders"],"summary":"List shaders","description":"This workspace's shaders and the shared ones, newest first, without variant text.","operationId":"list_shaders","security":[{"ApiKey":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":20,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CursorPage_ShaderOut_"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/shaders/{shader_id}":{"get":{"tags":["shaders"],"summary":"Get a shader","description":"One shader; `?view=full` adds its source and the compiled variants' text.","operationId":"get_shader","security":[{"ApiKey":[]}],"parameters":[{"name":"shader_id","in":"path","required":true,"schema":{"type":"string","title":"Shader Id"}},{"name":"view","in":"query","required":false,"schema":{"anyOf":[{"const":"full","type":"string"},{"type":"null"}],"description":"`full` adds `source` and `variants`.","title":"View"},"description":"`full` adds `source` and `variants`."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShaderOut"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Shader not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"delete":{"tags":["shaders"],"summary":"Delete a shader","description":"Remove one of this workspace's shaders. Renders already made with it are unaffected (a render carries the compiled variants); a document that still names it is refused with `shader_not_found` at its next render.","operationId":"delete_shader","security":[{"ApiKey":[]}],"parameters":[{"name":"shader_id","in":"path","required":true,"schema":{"type":"string","title":"Shader Id"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Shader not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"A shared shader cannot be deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/shaders/{shader_id}/probe":{"post":{"tags":["shaders"],"summary":"Render one frame of a shader","description":"Queue a one-frame preview (a PNG, not billed) of the shader on a full-canvas shape over a three-hue fill, at `time` with `params`. Poll the returned job with GET /v1/renders/{id}, as for any preview.","operationId":"probe_shader","security":[{"ApiKey":[]}],"parameters":[{"name":"shader_id","in":"path","required":true,"schema":{"type":"string","title":"Shader Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShaderProbe"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobOut"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Shader not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"`shader_compile_error`, `shader_param_invalid`, or `unsupported_feature` while this deployment does not send runtime shaders to the engine.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/tasks/transcribe":{"post":{"tags":["tasks"],"summary":"Transcribe task","description":"Submit a media URL for transcription. Runs asynchronously — the task is accepted with `202` and moves through `queued` → `processing` → a terminal status; poll `GET /v1/tasks/{task_id}` or pass `wait` there to long-poll. Supports word-level timestamps and speaker diarisation.","operationId":"create_transcribe","security":[{"ApiKey":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranscribeBody"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobOut"}}}},"200":{"description":"Idempotent replay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobOut"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation error or unsupported feature.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/tasks":{"get":{"tags":["tasks"],"summary":"List tasks","description":"Returns a cursor-paginated list of tasks for the workspace. Filter by type or status.","operationId":"list_tasks","security":[{"ApiKey":[]}],"parameters":[{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/TaskType"},{"type":"null"}],"title":"Type"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/JobStatus"},{"type":"null"}],"title":"Status"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":20,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CursorPage_JobOut_"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/tasks/{task_id}":{"get":{"tags":["tasks"],"summary":"Get a task","description":"Fetch a task's status. Pass `wait` (seconds) to long-poll: the request holds until the task reaches a terminal status (`completed`/`failed`/`cancelled`) or the hold elapses, then returns the current status — one call instead of a poll loop. When it returns still in-flight, `Retry-After: 0` is set; re-call to keep waiting.","operationId":"get_task","security":[{"ApiKey":[]}],"parameters":[{"name":"task_id","in":"path","required":true,"schema":{"type":"string","title":"Task Id"}},{"name":"wait","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Seconds to block until the task is terminal (capped server-side; values above the cap are clamped). 0 (default) returns immediately.","default":0,"title":"Wait"},"description":"Seconds to block until the task is terminal (capped server-side; values above the cap are clamped). 0 (default) returns immediately."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobOut"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Task not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"delete":{"tags":["tasks"],"summary":"Cancel a task","description":"Cancel a task job. Best-effort — a task already picked up by a worker may finish before the cancel signal is observed. A no-op if the task has already reached a terminal status (`completed`, `failed`, or `cancelled`).","operationId":"cancel_task","security":[{"ApiKey":[]}],"parameters":[{"name":"task_id","in":"path","required":true,"schema":{"type":"string","title":"Task Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobOut"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Task not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/tasks/{task_id}/download":{"get":{"tags":["tasks"],"summary":"Download task artifact","description":"Redirects to a short-lived signed GCS URL for the completed task artifact.\n\nOnly for task types that produce a **file**. `transcribe` — the only task type today — returns its transcript inline as the `result` object on `GET /v1/tasks/{id}`, so this endpoint answers `404` for it: there is no file to download, and no amount of waiting will produce one. A `422` here means the task simply has not finished yet.","operationId":"download_task","security":[{"ApiKey":[]}],"parameters":[{"name":"task_id","in":"path","required":true,"schema":{"type":"string","title":"Task Id"}}],"responses":{"302":{"description":"Redirect to a 5-minute signed URL."},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Task not found, or the task has no file artifact to download.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Task not yet completed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/uploads":{"post":{"tags":["uploads"],"summary":"Request a file upload URL","description":"Returns a GCS resumable upload URL and the corresponding CDN source URL. PUT your file bytes directly to `upload_url` with the correct `Content-Type` header. Once the upload completes, pass `source_url` as the `source_url` field in any render or task request.\n\nThe `upload_url` expires in **1 hour**. If your network refuses `storage.googleapis.com`, `first_party_upload_url` is the same session on a Framelane hostname; PUT there instead.\n\nAlternatively, send the file's bytes as base64 in `content_base64` and this one call stores them: the response comes back with `uploaded: true` and a null `upload_url`, and `source_url` works immediately. That route needs no outbound network access of your own — the reason it exists — but it is capped at 256 KB, so it suits logos and icons rather than video.","operationId":"create_upload","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadOut"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"413":{"description":"content_base64 exceeds the inline upload limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Unsupported content_type, or undecodable content_base64.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"ApiKey":[]}]}},"/v1/uploads/links":{"post":{"tags":["uploads"],"summary":"Create an upload link for a person to use","description":"Returns a URL valid for **one hour**. Anyone who opens it in a browser can upload one video, audio, image or font file into this workspace; the page reads the file's type and duration itself, so nothing has to be declared up front. Poll `GET /v1/uploads/links/{id}` to learn which asset the upload produced.\n\nUse this when you cannot PUT to storage yourself (a sandbox that blocks outbound hosts) and the host you run in cannot show an interactive upload panel, but a person is present. Never paste a raw `upload_url` into a chat instead: it is a live storage credential, and this page is what it would have to become anyway.","operationId":"create_upload_link","responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadLinkOut"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"ApiKey":[]}]}},"/v1/uploads/links/{link_id}":{"get":{"tags":["uploads"],"summary":"What an upload link has produced","operationId":"get_upload_link","security":[{"ApiKey":[]}],"parameters":[{"name":"link_id","in":"path","required":true,"schema":{"type":"string","title":"Link Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadLinkStatusOut"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such link.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/upload-links/{token}/uploads":{"post":{"tags":["uploads"],"summary":"Mint the slot behind an upload link (called by the upload page)","description":"Authenticated by the link token alone — this is what the upload page calls once the person has picked a file. Returns the same shape as `POST /v1/uploads`; the page then PUTs the bytes to `first_party_upload_url` (or `upload_url`). Calling it again re-points the link at a fresh slot, so a retried upload just works.","operationId":"mint_link_upload","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkUploadRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadOut"}}}},"404":{"description":"Unknown or expired link.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Unsupported content_type.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/webhooks":{"post":{"tags":["webhooks"],"summary":"Register webhook","description":"Register an HTTPS endpoint to receive job events. The signing secret is returned once — use it to verify HMAC signatures on payloads.","operationId":"create_webhook","security":[{"ApiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookCreated"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"get":{"tags":["webhooks"],"summary":"List webhooks","description":"Returns a cursor-paginated list of webhook endpoints registered for the workspace.","operationId":"list_webhooks","security":[{"ApiKey":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CursorPage_WebhookOut_"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/webhooks/{webhook_id}":{"get":{"tags":["webhooks"],"summary":"Get webhook","description":"Fetch a single webhook endpoint's configuration.","operationId":"get_webhook","security":[{"ApiKey":[]}],"parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","title":"Webhook Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookOut"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Webhook not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"patch":{"tags":["webhooks"],"summary":"Update webhook","description":"Update a webhook's URL, subscribed event types, or enabled state. Omitted fields are left unchanged.","operationId":"update_webhook","security":[{"ApiKey":[]}],"parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","title":"Webhook Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookOut"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Webhook not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"delete":{"tags":["webhooks"],"summary":"Delete webhook","description":"Permanently remove a webhook endpoint. Framelane stops delivering events to it immediately.","operationId":"delete_webhook","security":[{"ApiKey":[]}],"parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","title":"Webhook Id"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Webhook not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/webhooks/{webhook_id}/test":{"post":{"tags":["webhooks"],"summary":"Send test event","description":"Send a synthetic `webhook.test` event to the endpoint and return the delivery result, so you can verify the URL and signature handling before relying on real job events.","operationId":"test_webhook","security":[{"ApiKey":[]}],"parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","title":"Webhook Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeliveryOut"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Webhook not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/webhooks/{webhook_id}/rotate-secret":{"post":{"tags":["webhooks"],"summary":"Rotate signing secret","description":"Rotate the workspace signing secret. Framelane signs every delivery with a single workspace-level secret, so rotation re-keys all webhook endpoints and per-request `webhook_url` callbacks in the workspace — not just this one.","operationId":"rotate_secret","security":[{"ApiKey":[]}],"parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","title":"Webhook Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookRotateResponse"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Webhook not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/webhooks/{webhook_id}/deliveries":{"get":{"tags":["webhooks"],"summary":"List delivery attempts","description":"Returns the delivery history for a webhook endpoint, newest first.","operationId":"list_deliveries","security":[{"ApiKey":[]}],"parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","title":"Webhook Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CursorPage_WebhookDeliveryOut_"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Webhook not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/webhooks/{webhook_id}/deliveries/{delivery_id}/retry":{"post":{"tags":["webhooks"],"summary":"Retry a delivery","description":"Manually retry a failed webhook delivery. Creates a new delivery record.","operationId":"retry_delivery","security":[{"ApiKey":[]}],"parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","title":"Webhook Id"}},{"name":"delivery_id","in":"path","required":true,"schema":{"type":"string","title":"Delivery Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeliveryOut"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Webhook not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/billing/portal":{"post":{"tags":["billing"],"summary":"Create a Paddle Customer Portal session","description":"Returns a short-lived URL to the Paddle Customer Portal where the user can manage their subscription, payment method, and invoices.","operationId":"create_billing_portal","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingPortalResponse"}}}},"402":{"description":"No Paddle customer linked to this workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Paddle service unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"ApiKey":[]}]}}},"components":{"schemas":{"AddElementOp":{"properties":{"op":{"type":"string","const":"add_element","title":"Op"},"element":{"additionalProperties":true,"type":"object","title":"Element","description":"A full element object; validated as a timeline element, or with `parent` as a frame child (design px from the frame's top-left, and an `id`)."},"index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Index","description":"Insert position among the parent's children; appends when omitted."},"parent":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent","description":"A frame id: insert into its `children`. Omitted: the root `elements`."}},"additionalProperties":false,"type":"object","required":["op","element"],"title":"AddElementOp"},"AnimationKeyframe":{"properties":{"time":{"type":"number","maximum":1.0,"minimum":0.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.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.\n\nProperty channels mirror the engine's catalog file format, so anything a\nshipped preset expresses is expressible here: position (`translate`, curved\nvia `path`), uniform or per-axis `scale`, scalar z `rotate` or full 3D\n`rotation`, `opacity`/`color` tint, per-glyph `blur` (text registries only),\nand the full easing vocabulary including the parameterized families\n(`bezier` / `spring` / `back` / `elastic`)."},"ApiKeyCreate":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name","description":"Human-readable label for the new API key.","example":"production"}},"additionalProperties":false,"type":"object","required":["name"],"title":"ApiKeyCreate"},"ApiKeyCreated":{"properties":{"id":{"type":"string","title":"Id","description":"Unique API key ID.","example":"key_01J8QR2K5VKDGN2T4FBM3CZYX9"},"name":{"type":"string","title":"Name","description":"Human-readable label for this key.","example":"production"},"key_prefix":{"type":"string","title":"Key Prefix","description":"First 8 characters of the key, shown for identification.","example":"fl_live_k"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"ISO-8601 UTC timestamp when this key was created."},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At","description":"ISO-8601 UTC timestamp of the most recent authenticated request."},"revoked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Revoked At","description":"ISO-8601 UTC timestamp when this key was revoked. Null if still active."},"key":{"type":"string","title":"Key","description":"Plaintext API key. Store it securely — this is the only time it is returned.","example":"fl_live_kT9uQw..."}},"type":"object","required":["id","name","key_prefix","created_at","key"],"title":"ApiKeyCreated","description":"Returned once at creation time -- includes the plaintext secret."},"ApiKeyOut":{"properties":{"id":{"type":"string","title":"Id","description":"Unique API key ID.","example":"key_01J8QR2K5VKDGN2T4FBM3CZYX9"},"name":{"type":"string","title":"Name","description":"Human-readable label for this key.","example":"production"},"key_prefix":{"type":"string","title":"Key Prefix","description":"First 8 characters of the key, shown for identification.","example":"fl_live_k"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"ISO-8601 UTC timestamp when this key was created."},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At","description":"ISO-8601 UTC timestamp of the most recent authenticated request."},"revoked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Revoked At","description":"ISO-8601 UTC timestamp when this key was revoked. Null if still active."}},"type":"object","required":["id","name","key_prefix","created_at"],"title":"ApiKeyOut"},"ApplyOpsRequest":{"properties":{"ops":{"items":{"oneOf":[{"$ref":"#/components/schemas/ShiftElementOp"},{"$ref":"#/components/schemas/SetFieldsOp"},{"$ref":"#/components/schemas/TrimElementOp"},{"$ref":"#/components/schemas/SwapSourceOp"},{"$ref":"#/components/schemas/AddElementOp"},{"$ref":"#/components/schemas/RemoveElementOp"},{"$ref":"#/components/schemas/ReorderOp"},{"$ref":"#/components/schemas/SetTransitionOp"},{"$ref":"#/components/schemas/RemoveTransitionOp"},{"$ref":"#/components/schemas/SetRequestFieldsOp"},{"$ref":"#/components/schemas/ReplaceRequestOp"},{"$ref":"#/components/schemas/MoveElementOp"},{"$ref":"#/components/schemas/ApplyTemplateOp"}],"discriminator":{"propertyName":"op","mapping":{"add_element":"#/components/schemas/AddElementOp","apply_template":"#/components/schemas/ApplyTemplateOp","move_element":"#/components/schemas/MoveElementOp","remove_element":"#/components/schemas/RemoveElementOp","remove_transition":"#/components/schemas/RemoveTransitionOp","reorder":"#/components/schemas/ReorderOp","replace_request":"#/components/schemas/ReplaceRequestOp","set_fields":"#/components/schemas/SetFieldsOp","set_request_fields":"#/components/schemas/SetRequestFieldsOp","set_transition":"#/components/schemas/SetTransitionOp","shift_element":"#/components/schemas/ShiftElementOp","swap_source":"#/components/schemas/SwapSourceOp","trim_element":"#/components/schemas/TrimElementOp"}}},"type":"array","minItems":1,"title":"Ops","description":"Targeted edits applied in order; the batch is atomic."},"if_version":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"If Version","description":"Optimistic concurrency: reject with 409 if the head version differs.","example":3},"author":{"$ref":"#/components/schemas/RevisionAuthor","description":"Who authored this edit, for version provenance.","default":"agent"},"run_id":{"anyOf":[{"type":"string","maxLength":128},{"type":"null"}],"title":"Run Id","description":"Agent run correlation id, stamped on the new version."}},"additionalProperties":false,"type":"object","required":["ops"],"title":"ApplyOpsRequest"},"ApplyOpsResult":{"properties":{"version":{"type":"integer","title":"Version","description":"The new head version."},"editor_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Editor Url","description":"Open the edited project in the authenticated console."},"ok":{"type":"boolean","title":"Ok","description":"True when the new head has no error-severity violations."},"changed_ids":{"items":{"type":"string"},"type":"array","title":"Changed Ids","description":"Element ids the ops touched."},"diff":{"$ref":"#/components/schemas/ProjectDiff"},"violations":{"items":{"$ref":"#/components/schemas/Violation"},"type":"array","title":"Violations","description":"Validation riding along free: cross-element and whole-timeline invariant checks. Truncated to the first 36 findings (errors first) on very large compositions, with a VIOLATIONS_TRUNCATED marker carrying the full count; the free dry run is never truncated."}},"type":"object","required":["version","ok","diff"],"title":"ApplyOpsResult","description":"The result of an ops batch: never echoes the whole request."},"ApplyTemplateOp":{"properties":{"op":{"type":"string","const":"apply_template","title":"Op"},"template_id":{"type":"string","title":"Template Id","description":"A template you can read: one of yours, or a published one from GET /v1/templates."},"keep_output_settings":{"type":"boolean","title":"Keep Output Settings","description":"Replace the composition with the template's. True keeps this project's frame_rate, output_format, output_filename, alpha, webhook_url and metadata; everything else (canvas, duration, background, elements, transitions, groups, animations) comes from the template. False takes the template's head whole.","default":true}},"additionalProperties":false,"type":"object","required":["op","template_id"],"title":"ApplyTemplateOp"},"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.0,"minimum":0.0},{"type":"null"}],"title":"Track","description":"Timeline track index (0–255). Informational only; not used by the renderer."},"time":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Time","description":"When this element starts on the output timeline, in seconds. Defaults to `0` when omitted.","example":0.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`). To keep the track but silence it, set `muted: true`.","default":true},"locked":{"type":"boolean","title":"Locked","description":"Marks the element locked in editors. Not sent to the renderer.","default":false},"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.0},{"type":"null"}],"title":"In Point","description":"In point — seconds into the source file to start from. Defaults to `0`.","example":0.0},"out_point":{"anyOf":[{"type":"number","exclusiveMinimum":0.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.0},"speed":{"type":"number","maximum":4.0,"minimum":0.25,"title":"Speed","description":"Playback speed. `1.0` = normal speed, `2.0` = 2× speed.","default":1.0,"example":1.0},"volume":{"type":"number","maximum":100.0,"exclusiveMinimum":0.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`. Use `muted` to silence the track.","default":100,"example":100},"muted":{"type":"boolean","title":"Muted","description":"Silence this track while keeping it, and its `volume`, in the composition. A muted track is left out of the render's audio.","default":false},"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"},"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).\n\nEach frame the mix reduces to the chosen feature and the element's properties\nget offset by ``amount × feature`` — beat-bounce, bass-pumped scale, etc."},"AuthSyncRequest":{"properties":{"clerk_token":{"type":"string","title":"Clerk Token"}},"additionalProperties":false,"type":"object","required":["clerk_token"],"title":"AuthSyncRequest"},"AuthSyncResponse":{"properties":{"workspace":{"$ref":"#/components/schemas/WorkspaceOut"},"is_new":{"type":"boolean","title":"Is New"},"api_key":{"anyOf":[{"$ref":"#/components/schemas/ApiKeyCreated"},{"type":"null"}]}},"type":"object","required":["workspace","is_new"],"title":"AuthSyncResponse"},"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')."},"BezierEasing":{"properties":{"x1":{"type":"number","title":"X1","default":0.0},"y1":{"type":"number","title":"Y1","default":0.0},"x2":{"type":"number","title":"X2","default":1.0},"y2":{"type":"number","title":"Y2","default":1.0}},"additionalProperties":false,"type":"object","title":"BezierEasing","description":"CSS cubic-bezier(x1, y1, x2, y2) timing curve."},"BillingPortalResponse":{"properties":{"url":{"type":"string","title":"Url"}},"type":"object","required":["url"],"title":"BillingPortalResponse"},"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:\n- `none` — Normal compositing (source-over) — the default.\n- `multiply` — Multiply blend — darkens by multiplying layer colors.\n- `screen` — Screen blend — lightens by inverting, multiplying, inverting.\n- `overlay` — Overlay blend — multiply in shadows, screen in highlights.\n- `darken` — Darken blend — keeps the darker of the two layers per channel.\n- `lighten` — Lighten blend — keeps the lighter of the two layers per channel.\n- `color_dodge` — Color-dodge blend — brightens the base toward the blend color.\n- `color_burn` — Color-burn blend — darkens the base toward the blend color.\n- `hard_light` — Hard-light blend — overlay with layers swapped.\n- `soft_light` — Soft-light blend — a gentler hard-light.\n- `difference` — Difference blend — absolute difference of the two layers per channel.\n- `add` — Additive (linear-dodge) blend — sums layer colors, clamped.\n- `exclusion` — Exclusion blend — like difference, lower contrast.\n- `hue` — Hue blend — backdrop luminosity + saturation with the blend layer's hue.\n- `saturation` — Saturation blend — backdrop hue + luminosity with the blend layer's saturation.\n- `color` — Color blend — backdrop luminosity with the blend layer's hue + saturation.\n- `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."}},"BlurKeyframe":{"properties":{"time":{"type":"number","minimum":0.0,"title":"Time","description":"Timeline seconds."},"value":{"type":"number","maximum":128.0,"minimum":0.0,"title":"Value","description":"Gaussian sigma in canvas px at `time`, at most 128."}},"additionalProperties":false,"type":"object","required":["time","value"],"title":"BlurKeyframe","description":"A blur sigma at a time (V6): linear between keyframes, the first and last held\noutside them. Any order: the renderer sorts them by time."},"BrandKitExtractRequest":{"properties":{"url":{"type":"string","maxLength":2083,"minLength":1,"format":"uri","title":"Url","description":"Public website URL to extract brand tokens from."},"name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Name","description":"Optional label; defaults to the site's domain."}},"additionalProperties":false,"type":"object","required":["url"],"title":"BrandKitExtractRequest","description":"``POST /v1/brand-kits`` — extract a kit from a website."},"BrandKitOut":{"properties":{"id":{"type":"string","title":"Id","description":"Unique brand kit id.","example":"bk_01J8QR2K5VKDGN2T4FBM3CZYX7"},"workspace_id":{"type":"string","title":"Workspace Id"},"name":{"type":"string","title":"Name"},"source_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Url","description":"URL the kit was extracted from."},"palette":{"$ref":"#/components/schemas/Palette"},"fonts":{"$ref":"#/components/schemas/Fonts"},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Url"},"corner_feel":{"anyOf":[{"type":"string","enum":["sharp","soft","pill"]},{"type":"null"}],"title":"Corner Feel"},"confidence":{"additionalProperties":{"type":"number"},"type":"object","title":"Confidence","description":"Per-field extraction confidence 0–1 (e.g. {'palette.primary': 0.8})."},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","workspace_id","name","created_at","updated_at"],"title":"BrandKitOut"},"BrandKitUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Name"},"palette":{"anyOf":[{"$ref":"#/components/schemas/Palette"},{"type":"null"}]},"fonts":{"anyOf":[{"$ref":"#/components/schemas/Fonts"},{"type":"null"}]},"logo_url":{"anyOf":[{"type":"string","maxLength":2083,"minLength":1,"format":"uri"},{"type":"null"}],"title":"Logo Url"},"corner_feel":{"anyOf":[{"type":"string","enum":["sharp","soft","pill"]},{"type":"null"}],"title":"Corner Feel"}},"additionalProperties":false,"type":"object","title":"BrandKitUpdate","description":"``PATCH`` — override any field the extractor got wrong. Provided sub-objects\nmerge field-by-field (a null leaves the stored value untouched)."},"CapabilitiesResponse":{"properties":{"api_version":{"type":"string","title":"Api Version","description":"API build version this catalog reflects."},"skills":{"additionalProperties":{"additionalProperties":true,"type":"object"},"type":"object","title":"Skills","description":"The published workflow corpus keyed by skill name: its description, verification status, Markdown content and content hash."},"violation_codes":{"items":{"additionalProperties":{"type":"string"},"type":"object"},"type":"array","title":"Violation Codes","description":"Every lint code a preview or render receipt can report."},"onboarding":{"$ref":"#/components/schemas/Onboarding","description":"How to self-provision an API key (agent-native signup flow)."},"output_formats":{"items":{"$ref":"#/components/schemas/OutputFormatCap"},"type":"array","title":"Output Formats"},"input_formats":{"items":{"$ref":"#/components/schemas/InputFormatCap"},"type":"array","title":"Input Formats"},"element_types":{"items":{"$ref":"#/components/schemas/EnumValueCap"},"type":"array","title":"Element Types"},"element_schemas":{"additionalProperties":{"additionalProperties":true,"type":"object"},"type":"object","title":"Element Schemas","description":"Full JSON Schema for every field each element type accepts, keyed by element type value (e.g. 'text', 'video') — everything `element_types` doesn't cover (accepted fields, types, defaults, constraints, enum options), including nested sub-objects like `motion`/`effects`/`word_animation` inlined under each entry's own `$defs`. Generated directly from the same Pydantic models the API validates requests against, so it can't drift out of sync."},"request_schema":{"additionalProperties":true,"type":"object","title":"Request Schema","description":"Full JSON Schema of the render request body itself — the composition-level surface `element_schemas` doesn't cover: `groups` (transform groups, nesting, stack layout), `custom_animations` (inline keyframe animation registries), `motion_blur`, `background_gradient`, `transitions`, canvas and output settings. The `elements` array's per-type schemas are NOT duplicated here — read them from `element_schemas`. Generated from the same Pydantic model the API validates against, so it can't drift out of sync."},"blocks":{"additionalProperties":{"additionalProperties":true,"type":"object"},"type":"object","title":"Blocks","description":"Self-contained JSON Schema per block, keyed by block name (e.g. 'geometry', a shape element's `geometry`): each carries only the `$defs` it reaches, so one block can be read without its element schema. Generated from the same Pydantic models the API validates against."},"registry":{"additionalProperties":true,"type":"object","title":"Registry","description":"The property registry an operation's `property` names (render-node's, vendored and hash-checked): `properties` maps each path to its value type, unit, range, clamp, phase, composites, element kinds and drawn kinds; `aliases` maps alias spellings to paths; `lint_codes` names the path diagnostics; `fnv1a64` is the registry's hash."},"shaders":{"$ref":"#/components/schemas/ShaderCaps","description":"Runtime shaders: the `effects[].shader` entry, its param types and limits, and the authoring loop."},"effects":{"items":{"$ref":"#/components/schemas/EnumValueCap"},"type":"array","title":"Effects"},"motions":{"items":{"$ref":"#/components/schemas/MotionCap"},"type":"array","title":"Motions"},"transitions":{"items":{"$ref":"#/components/schemas/EnumValueCap"},"type":"array","title":"Transitions"},"word_animations":{"items":{"$ref":"#/components/schemas/EnumValueCap"},"type":"array","title":"Word Animations"},"blend_modes":{"items":{"$ref":"#/components/schemas/EnumValueCap"},"type":"array","title":"Blend Modes"},"easings":{"items":{"$ref":"#/components/schemas/EnumValueCap"},"type":"array","title":"Easings"},"motion_scopes":{"items":{"$ref":"#/components/schemas/EnumValueCap"},"type":"array","title":"Motion Scopes"},"task_types":{"items":{"$ref":"#/components/schemas/TaskTypeCap"},"type":"array","title":"Task Types"},"limits":{"$ref":"#/components/schemas/Limits"},"rate_limits":{"items":{"$ref":"#/components/schemas/RateLimit"},"type":"array","title":"Rate Limits"}},"type":"object","required":["api_version","onboarding","output_formats","input_formats","element_types","element_schemas","request_schema","shaders","effects","motions","transitions","word_animations","blend_modes","easings","motion_scopes","task_types","limits","rate_limits"],"title":"CapabilitiesResponse","description":"The full capability catalog returned by ``GET /v1/capabilities``."},"ChildImageElement":{"properties":{"lut_url":{"anyOf":[{"type":"string","maxLength":2083,"minLength":1,"format":"uri"},{"type":"null"}],"title":"Lut Url"},"lut_intensity":{"type":"number","maximum":100.0,"minimum":0.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.0,"minimum":0.0,"title":"Crop Top","default":0},"crop_bottom":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Crop Bottom","default":0},"crop_left":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Crop Left","default":0},"crop_right":{"type":"number","maximum":1.0,"minimum":0.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","description":"Glass: the scene behind the element, Gaussian-blurred, fills its footprint under its translucent content (a shape's own silhouette, a video's or image's box with its corner radius and mask). A Gaussian sigma in canvas px, as CSS `backdrop-filter: blur()` takes it; the renderer caps it at 128. 0 is off.","default":0},"backdrop_blur_keyframes":{"anyOf":[{"items":{"$ref":"#/components/schemas/BlurKeyframe"},"type":"array","minItems":1},{"type":"null"}],"title":"Backdrop Blur Keyframes","description":"Animate `backdrop_blur` over the timeline; replaces the static value while present."},"layer_blur":{"type":"number","maximum":128.0,"minimum":0.0,"title":"Layer Blur","description":"Blurs the element itself as drawn: a Gaussian sigma in canvas px, as CSS `filter: blur()` takes it, spreading three sigma past its box. At most 128; 0 is off. Not on a shape yet (the editor's preview cannot draw it), and not with `blend_mode`, `backdrop_blur` or `matte`, beside which the renderer would draw the element without it.","default":0},"layer_blur_keyframes":{"anyOf":[{"items":{"$ref":"#/components/schemas/BlurKeyframe"},"type":"array","minItems":1},{"type":"null"}],"title":"Layer Blur Keyframes","description":"Animate `layer_blur` over the timeline; replaces the static value while present."},"glass":{"anyOf":[{"$ref":"#/components/schemas/Glass"},{"type":"null"}],"description":"A refracting glass edge: the scene behind bends inward near the element's outline. Pair it with `backdrop_blur` for frosted glass."},"x":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"X","description":"Left edge in design px from the frame's left edge. Omitted: 0."},"y":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Y","description":"Top edge in design px from the frame's top edge. Omitted: 0."},"width":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Width","description":"Width in design px. Omitted: the frame's width."},"height":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Height","description":"Height in design px. Omitted: the frame's height (a text's: twice its `font_size`)."},"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":"Pivot the element turns and scales about, across its own box: a percent, or design px from its left edge.","default":"50%"},"y_anchor":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"string"}],"title":"Y Anchor","description":"Pivot across its own box's height: a percent, or design px from its top edge.","default":"50%"},"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.0,"minimum":0.0,"title":"Opacity","default":100},"z_index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Z Index","description":"Stacking order among the frame's children (higher on top); ties draw in array order."},"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.0,"minimum":0.0},{"type":"null"}],"title":"Track","description":"Timeline track index (0–255). Informational only; not used by the renderer."},"time":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Time","description":"Start in seconds on the clock the frame gives its children (the composition's, unless a composite frame maps it; inside a component, 0 is the instance's start). Omitted: the frame's start."},"visible":{"type":"boolean","title":"Visible","description":"Set to `false` to skip this element without removing it from the request.","default":true},"locked":{"type":"boolean","title":"Locked","description":"Marks the element locked in editors. Not sent to the renderer.","default":false},"duration":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Duration","description":"Seconds visible. Omitted: until the frame ends."},"source_url":{"type":"string","maxLength":2083,"minLength":1,"format":"uri","title":"Source Url","description":"URL of the image file (PNG, JPG, WebP, GIF, SVG). A `.svg` draws a subset: groups and the basic shapes and paths, filled and stroked with flat colours. An element that uses anything else (a `style` attribute, `clip-path`, `mask`, `filter`, `stroke-dasharray`, a gradient or `currentColor` paint, `<text>`, `<use>`, `<image>`) is not drawn, nor anything inside it; an SVG that would draw nothing at all is refused at submit. Set explicit colours and presentation attributes, or pre-rasterize 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","source_url"],"title":"ChildImageElement","description":"An image inside a frame."},"ChildShapeElement":{"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","description":"Glass: the scene behind the element, Gaussian-blurred, fills its footprint under its translucent content (a shape's own silhouette, a video's or image's box with its corner radius and mask). A Gaussian sigma in canvas px, as CSS `backdrop-filter: blur()` takes it; the renderer caps it at 128. 0 is off.","default":0},"backdrop_blur_keyframes":{"anyOf":[{"items":{"$ref":"#/components/schemas/BlurKeyframe"},"type":"array","minItems":1},{"type":"null"}],"title":"Backdrop Blur Keyframes","description":"Animate `backdrop_blur` over the timeline; replaces the static value while present."},"layer_blur":{"type":"number","maximum":128.0,"minimum":0.0,"title":"Layer Blur","description":"Blurs the element itself as drawn: a Gaussian sigma in canvas px, as CSS `filter: blur()` takes it, spreading three sigma past its box. At most 128; 0 is off. Not on a shape yet (the editor's preview cannot draw it), and not with `blend_mode`, `backdrop_blur` or `matte`, beside which the renderer would draw the element without it.","default":0},"layer_blur_keyframes":{"anyOf":[{"items":{"$ref":"#/components/schemas/BlurKeyframe"},"type":"array","minItems":1},{"type":"null"}],"title":"Layer Blur Keyframes","description":"Animate `layer_blur` over the timeline; replaces the static value while present."},"glass":{"anyOf":[{"$ref":"#/components/schemas/Glass"},{"type":"null"}],"description":"A refracting glass edge: the scene behind bends inward near the element's outline. Pair it with `backdrop_blur` for frosted glass."},"x":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"X","description":"Left edge in design px from the frame's left edge. Omitted: 0."},"y":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Y","description":"Top edge in design px from the frame's top edge. Omitted: 0."},"width":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Width","description":"Width in design px. Omitted: the frame's width."},"height":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Height","description":"Height in design px. Omitted: the frame's height (a text's: twice its `font_size`)."},"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":"Pivot the element turns and scales about, across its own box: a percent, or design px from its left edge.","default":"50%"},"y_anchor":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"string"}],"title":"Y Anchor","description":"Pivot across its own box's height: a percent, or design px from its top edge.","default":"50%"},"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.0,"minimum":0.0,"title":"Opacity","default":100},"z_index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Z Index","description":"Stacking order among the frame's children (higher on top); ties draw in array order."},"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.0,"minimum":0.0},{"type":"null"}],"title":"Track","description":"Timeline track index (0–255). Informational only; not used by the renderer."},"time":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Time","description":"Start in seconds on the clock the frame gives its children (the composition's, unless a composite frame maps it; inside a component, 0 is the instance's start). Omitted: the frame's start."},"visible":{"type":"boolean","title":"Visible","description":"Set to `false` to skip this element without removing it from the request.","default":true},"locked":{"type":"boolean","title":"Locked","description":"Marks the element locked in editors. Not sent to the renderer.","default":false},"duration":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Duration","description":"Seconds visible. Omitted: until the frame ends."},"path":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Path","description":"SVG path `d` (M L H V C S Q T A Z; elliptical arcs render as arcs). **Required** unless `geometry` is set.","example":"M50 0 L100 100 L0 100 Z"},"geometry":{"anyOf":[{"$ref":"#/components/schemas/ShapeGeometry"},{"type":"null"}],"description":"Draw a closed-form shape instead of a `path`: exactly one of `rect` (`corner_radius` per corner, `smoothing`), `ellipse`, `line`, `ring` or `arc` (`inner_ratio`, `start_angle`, `sweep`, `cap`, `segments`), `polygon` (`sides`, `corner_radius`) or `star` (`points`, `inner_ratio`, `corner_radius`), plus `stroke_align` and `shadows`. Lengths are px, angles degrees clockwise from 3 o'clock. The box, `fill`, `stroke`, `stroke_width`, `stroke_dash`, motion, masks, mattes, blend modes and effects work as on any shape; `path`, `path_keyframes`, `trim`, `corner_radius` and `shadow` do not apply."},"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,0.0,100.0,100.0]},"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","maximum":100000.0,"minimum":0.0},{"type":"null"}],"title":"Stroke Width","description":"Stroke width in viewBox units, at most 100000 (absent = SVG 1.0; 0 disables)."},"stroke_linecap":{"type":"string","enum":["butt","round","square"],"title":"Stroke Linecap","description":"Cap on the open ends of a stroked path (SVG `stroke-linecap`): `butt` stops at the endpoint, `round` and `square` extend it by half the stroke width. Closed paths have joins, not caps.","default":"round"},"stroke_linejoin":{"type":"string","enum":["miter","round","bevel"],"title":"Stroke Linejoin","description":"Corner join of a stroked path (SVG `stroke-linejoin`): `miter` is the sharp corner (subject to `stroke_miter_limit`), `bevel` cuts it flat.","default":"round"},"stroke_miter_limit":{"type":"number","minimum":1.0,"title":"Stroke Miter Limit","description":"SVG `stroke-miterlimit`: miter length over stroke width. A `miter` join sharper than this falls back to `bevel`.","default":4.0},"stroke_dash":{"anyOf":[{"items":{"type":"number"},"type":"array","minItems":1},{"type":"null"}],"title":"Stroke Dash","description":"Dash pattern in viewBox units, SVG `stroke-dasharray` semantics: `[on, off, ...]` of any length — an odd-length array repeats so on/off alternate (`[4]` is 4 on, 4 off). Trim applies before dash, so a dashed path can draw itself on without the pattern re-flowing."},"stroke_dash_offset":{"type":"number","title":"Stroke Dash Offset","description":"Where the dash pattern starts along the path, in viewBox units (SVG `stroke-dashoffset`): positive shifts the pattern forward, negative backward.","default":0.0},"stroke_dash_offset_keyframes":{"anyOf":[{"items":{"$ref":"#/components/schemas/DashOffsetKeyframe"},"type":"array"},{"type":"null"}],"title":"Stroke Dash Offset Keyframes","description":"Animate the dash offset (timeline seconds) — marching ants: keyframe `offset` linearly by one dash period (the pattern's sum) per second. Needs a `stroke_dash`; while present it drives the offset in place of the static `stroke_dash_offset`."},"stroke_marker_start":{"type":"string","enum":["none","triangle","open_chevron","dot"],"title":"Stroke Marker Start","description":"A head at the start of each open subpath, in the stroke's colour (render-node V2.6): `triangle` is filled, its tip on the endpoint (the shaft shortens to meet it); `open_chevron` is an open head stroked like the path; `dot` is a disc. Placed after the trim, so a draw-on keeps its head at the visible end, and left out of the dash. A closed subpath takes none. Needs a `stroke`; path shapes only.","default":"none"},"stroke_marker_end":{"type":"string","enum":["none","triangle","open_chevron","dot"],"title":"Stroke Marker End","description":"A head at the end of each open subpath; see `stroke_marker_start`.","default":"none"},"stroke_marker_size":{"type":"number","maximum":32.0,"minimum":1.0,"title":"Stroke Marker Size","description":"Marker length in stroke widths, 1–32 (4 is four times `stroke_width`).","default":4.0},"corner_radius":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.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"],"title":"ChildShapeElement","description":"A shape inside a frame."},"ChildTextElement":{"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":"number"},{"type":"null"}],"title":"X","description":"Left edge in design px from the frame's left edge. Omitted: 0."},"y":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Y","description":"Top edge in design px from the frame's top edge. Omitted: 0."},"width":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Width","description":"Width in design px. Omitted: the frame's width."},"height":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Height","description":"Height in design px. Omitted: the frame's height (a text's: twice its `font_size`)."},"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":"Pivot the element turns and scales about, across its own box: a percent, or design px from its left edge.","default":"50%"},"y_anchor":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"string"}],"title":"Y Anchor","description":"Pivot across its own box's height: a percent, or design px from its top edge.","default":"50%"},"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.0,"minimum":0.0,"title":"Opacity","default":100},"z_index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Z Index","description":"Stacking order among the frame's children (higher on top); ties draw in array order."},"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.0,"minimum":0.0},{"type":"null"}],"title":"Track","description":"Timeline track index (0–255). Informational only; not used by the renderer."},"time":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Time","description":"Start in seconds on the clock the frame gives its children (the composition's, unless a composite frame maps it; inside a component, 0 is the instance's start). Omitted: the frame's start."},"visible":{"type":"boolean","title":"Visible","description":"Set to `false` to skip this element without removing it from the request.","default":true},"locked":{"type":"boolean","title":"Locked","description":"Marks the element locked in editors. Not sent to the renderer.","default":false},"duration":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Duration","description":"Seconds visible. Omitted: until the frame ends."},"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.0,"title":"Font Size","description":"Font size in pixels.","default":16,"example":48},"font_weight":{"type":"integer","maximum":900.0,"minimum":100.0,"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},"tabular_numbers":{"type":"boolean","title":"Tabular Numbers","description":"Tabular (monospaced) figures: every digit takes the same advance, so columns of numbers align across lines and a `counter` does not jitter as its value changes. The renderer's baked fonts carry the OpenType alternates; a font whose face lacks the feature gets synthesised tabular digits in the export only. Default `false` = the font's proportional figures.","default":false,"example":false},"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.0,"minimum":0.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},"background_style":{"type":"string","enum":["block","line_hard","line_round"],"title":"Background Style","description":"Shape of the `background` plate: `block` — one box behind the whole text; `line_hard` — a square-cornered box behind each line; `line_round` — a rounded box behind each line. Requires `background: true`. An animation that centres its words draws the `block` plate whichever style is set.","default":"block"},"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.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`, `preset` or `custom`) for structured timing control — but not both. Ignored when `word_animation` is set."}},"additionalProperties":false,"type":"object","required":["text"],"title":"ChildTextElement","description":"A text inside a frame; `width` is its wrap width."},"ChildVideoElement":{"properties":{"lut_url":{"anyOf":[{"type":"string","maxLength":2083,"minLength":1,"format":"uri"},{"type":"null"}],"title":"Lut Url"},"lut_intensity":{"type":"number","maximum":100.0,"minimum":0.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.0,"minimum":0.0,"title":"Crop Top","default":0},"crop_bottom":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Crop Bottom","default":0},"crop_left":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Crop Left","default":0},"crop_right":{"type":"number","maximum":1.0,"minimum":0.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","description":"Glass: the scene behind the element, Gaussian-blurred, fills its footprint under its translucent content (a shape's own silhouette, a video's or image's box with its corner radius and mask). A Gaussian sigma in canvas px, as CSS `backdrop-filter: blur()` takes it; the renderer caps it at 128. 0 is off.","default":0},"backdrop_blur_keyframes":{"anyOf":[{"items":{"$ref":"#/components/schemas/BlurKeyframe"},"type":"array","minItems":1},{"type":"null"}],"title":"Backdrop Blur Keyframes","description":"Animate `backdrop_blur` over the timeline; replaces the static value while present."},"layer_blur":{"type":"number","maximum":128.0,"minimum":0.0,"title":"Layer Blur","description":"Blurs the element itself as drawn: a Gaussian sigma in canvas px, as CSS `filter: blur()` takes it, spreading three sigma past its box. At most 128; 0 is off. Not on a shape yet (the editor's preview cannot draw it), and not with `blend_mode`, `backdrop_blur` or `matte`, beside which the renderer would draw the element without it.","default":0},"layer_blur_keyframes":{"anyOf":[{"items":{"$ref":"#/components/schemas/BlurKeyframe"},"type":"array","minItems":1},{"type":"null"}],"title":"Layer Blur Keyframes","description":"Animate `layer_blur` over the timeline; replaces the static value while present."},"glass":{"anyOf":[{"$ref":"#/components/schemas/Glass"},{"type":"null"}],"description":"A refracting glass edge: the scene behind bends inward near the element's outline. Pair it with `backdrop_blur` for frosted glass."},"x":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"X","description":"Left edge in design px from the frame's left edge. Omitted: 0."},"y":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Y","description":"Top edge in design px from the frame's top edge. Omitted: 0."},"width":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Width","description":"Width in design px. Omitted: the frame's width."},"height":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Height","description":"Height in design px. Omitted: the frame's height (a text's: twice its `font_size`)."},"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":"Pivot the element turns and scales about, across its own box: a percent, or design px from its left edge.","default":"50%"},"y_anchor":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"string"}],"title":"Y Anchor","description":"Pivot across its own box's height: a percent, or design px from its top edge.","default":"50%"},"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.0,"minimum":0.0,"title":"Opacity","default":100},"z_index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Z Index","description":"Stacking order among the frame's children (higher on top); ties draw in array order."},"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.0,"minimum":0.0},{"type":"null"}],"title":"Track","description":"Timeline track index (0–255). Informational only; not used by the renderer."},"time":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Time","description":"Start in seconds on the clock the frame gives its children (the composition's, unless a composite frame maps it; inside a component, 0 is the instance's start). Omitted: the frame's start."},"visible":{"type":"boolean","title":"Visible","description":"Set to `false` to skip this element without removing it from the request.","default":true},"locked":{"type":"boolean","title":"Locked","description":"Marks the element locked in editors. Not sent to the renderer.","default":false},"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.0},{"type":"null"}],"title":"In Point","description":"In point — seconds into the source file to start playing from. Defaults to `0`.","example":0.0},"out_point":{"anyOf":[{"type":"number","exclusiveMinimum":0.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.0},"speed":{"type":"number","maximum":4.0,"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.0,"example":1.0},"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.0,"minimum":0.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},"muted":{"type":"boolean","title":"Muted","description":"Silence the clip's embedded audio. `volume` is kept, so unmuting restores the level.","default":false},"loop":{"type":"boolean","title":"Loop","description":"Repeat the source to fill the clip: when playback passes the end of the file it restarts from the file's first frame (not `in_point`). Set `out_point` past the source's length to choose how long the clip runs — its timeline length is still `(out_point − in_point) / speed`. Requires `out_point`.","default":false},"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":"ChildVideoElement","description":"A video inside a frame. Not inside a composite frame, whose content is graphics only."},"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.0,"minimum":0.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.0,"minimum":0.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.0,"minimum":0.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.\n\nKeys against an explicit key **colour** in the YCbCr chroma plane, which is\nluma-independent, so shadows falling on the screen key out along with it.\n\nThis replaced an hue/saturation/luminance-window shape that could not work. It\nwas wired to ``effect_chroma.frag``, whose ``sensitivity`` is\n``(hueMax - hueMin) * 0.5`` compared against an Oklab distance bounded around\n0..0.9 — so the old defaults (60/180) produced a sensitivity of 60, discarded\nevery pixel, and made the element vanish from the render. Nothing could have\ndepended on the previous shape, because nothing rendered with it."},"Component":{"properties":{"id":{"type":"string","minLength":1,"title":"Id","description":"What an instance's `component` names.","example":"card"},"width":{"type":"number","exclusiveMinimum":0.0,"title":"Width","description":"Box width in design px.","example":552},"height":{"type":"number","exclusiveMinimum":0.0,"title":"Height","description":"Box height in design px.","example":400},"clip":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Clip","description":"Its instances clip to the box unless they say otherwise."},"corner_radius":{"anyOf":[{"type":"number","minimum":0.0},{"items":{"type":"number","minimum":0.0},"type":"array","maxItems":4,"minItems":4},{"$ref":"#/components/schemas/GeometryCornerRadii"},{"type":"null"}],"title":"Corner Radius","description":"Its instances' clip corner radius in design px."},"properties":{"items":{"$ref":"#/components/schemas/ComponentProperty"},"type":"array","title":"Properties","description":"What an instance can set.","default":[]},"children":{"anyOf":[{"items":{"oneOf":[{"$ref":"#/components/schemas/ChildVideoElement"},{"$ref":"#/components/schemas/ChildTextElement"},{"$ref":"#/components/schemas/ChildImageElement"},{"$ref":"#/components/schemas/ChildShapeElement"},{"$ref":"#/components/schemas/FrameElement"},{"$ref":"#/components/schemas/InstanceElement"}],"discriminator":{"propertyName":"type","mapping":{"frame":"#/components/schemas/FrameElement","image":"#/components/schemas/ChildImageElement","instance":"#/components/schemas/InstanceElement","shape":"#/components/schemas/ChildShapeElement","text":"#/components/schemas/ChildTextElement","video":"#/components/schemas/ChildVideoElement"}}},"type":"array"},{"type":"null"}],"title":"Children","description":"The body of a single-variant component, in design px from the box's top-left corner. Give this or `variants`."},"operations":{"anyOf":[{"$ref":"#/components/schemas/OperationsBlock"},{"type":"null"}],"description":"The body's operations (beside `children`); groups count from `instance.start` unless they say otherwise."},"variants":{"anyOf":[{"items":{"$ref":"#/components/schemas/ComponentVariant"},"type":"array","minItems":1},{"type":"null"}],"title":"Variants","description":"Named bodies, the first the default. Give this or `children`."}},"additionalProperties":false,"type":"object","required":["id","width","height"],"title":"Component","description":"A component definition (render-node AEC §5g): a precomp kept in the request, with a\nfixed box, typed properties bound to its body, variants and slots. `instance` elements\nplace it."},"ComponentProperty":{"properties":{"name":{"type":"string","pattern":"^[A-Za-z][A-Za-z0-9_]*$","title":"Name","description":"A letter, then letters, digits or `_`.","example":"title"},"type":{"type":"string","enum":["text","colour","number","boolean","media","component","variant"],"title":"Type","description":"`text`, `colour` (`#RRGGBB` or `#RRGGBBAA`), `number`, `boolean`, `media` (a URL), `component` (an id from `allowed`) or `variant` (a nested instance's)."},"bind":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Bind","description":"Body fields it writes, as `<child id>.<field>` in this API's field names: any element's `x`, `y`, `z_rotation`, `opacity` and `visible`; a frame's or instance's `scale` and `clip`; a text's `text`, `text_color` and `font_size`; a shape's `fill`, `stroke` and `stroke_width`; an image's or video's `source_url`; an instance's `component`, `variant` and `properties.<name>`.","example":["title.text"]},"default":{"anyOf":[{"type":"boolean"},{"type":"integer"},{"type":"number"},{"type":"string"},{"type":"null"}],"title":"Default","description":"Its value when an instance sets none. Omitted: the body's own."},"min":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Min","description":"A `number` property's lowest value."},"max":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Max","description":"A `number` property's highest value."},"allowed":{"anyOf":[{"items":{"type":"string"},"type":"array","minItems":1},{"type":"null"}],"title":"Allowed","description":"The component ids a `component` property may swap in. Required on one."}},"additionalProperties":false,"type":"object","required":["name","type","bind"],"title":"ComponentProperty","description":"A typed property an instance sets, written into the body fields it binds."},"ComponentVariant":{"properties":{"name":{"type":"string","minLength":1,"title":"Name","example":"compact"},"width":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Width","description":"Its box width in design px. Omitted: the component's."},"height":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Height","description":"Its box height in design px. Omitted: the component's."},"children":{"items":{"oneOf":[{"$ref":"#/components/schemas/ChildVideoElement"},{"$ref":"#/components/schemas/ChildTextElement"},{"$ref":"#/components/schemas/ChildImageElement"},{"$ref":"#/components/schemas/ChildShapeElement"},{"$ref":"#/components/schemas/FrameElement"},{"$ref":"#/components/schemas/InstanceElement"}],"discriminator":{"propertyName":"type","mapping":{"frame":"#/components/schemas/FrameElement","image":"#/components/schemas/ChildImageElement","instance":"#/components/schemas/InstanceElement","shape":"#/components/schemas/ChildShapeElement","text":"#/components/schemas/ChildTextElement","video":"#/components/schemas/ChildVideoElement"}}},"type":"array","title":"Children","description":"Its body, in design px from the box's top-left corner.","default":[]},"operations":{"anyOf":[{"$ref":"#/components/schemas/OperationsBlock"},{"type":"null"}],"description":"Its operations; groups count from `instance.start` unless they say otherwise."}},"additionalProperties":false,"type":"object","required":["name"],"title":"ComponentVariant","description":"One of a component's bodies; an instance picks it by name."},"ContactSheetCell":{"properties":{"index":{"type":"integer","title":"Index","description":"Position in the grid, row-major from 0."},"at":{"type":"number","title":"At","description":"Timeline seconds of this frame (snapped to the frame grid)."},"reason":{"$ref":"#/components/schemas/SampleReason","description":"Why this moment was sampled."}},"type":"object","required":["index","at","reason"],"title":"ContactSheetCell","description":"One frame in the grid: where it is on the timeline and why it was picked."},"ContactSheetOptions":{"properties":{"detail":{"type":"string","enum":["overview","balanced","detailed"],"title":"Detail","description":"Cell budget CEILING, not a target: overview 9, balanced 16, detailed 36. Actual count is ~1 cell/second of the sampled range (min 4), snapped up to fill a grid (4s->4, 6s->6), so all tiers match below ~10s.","default":"balanced"},"columns":{"anyOf":[{"type":"integer","maximum":12.0,"minimum":1.0},{"type":"null"}],"title":"Columns","description":"Grid columns. Defaults to a near-square layout."},"max_cells":{"anyOf":[{"type":"integer","maximum":64.0,"minimum":1.0},{"type":"null"}],"title":"Max Cells","description":"Hard cap on the number of cells, below the tier budget."}},"additionalProperties":false,"type":"object","title":"ContactSheetOptions","description":"Knobs for a contact-sheet preview. Range + cell width come from the parent\n``PreviewOptions`` (``window`` / ``width``)."},"ContactSheetPlan":{"properties":{"columns":{"type":"integer","title":"Columns"},"rows":{"type":"integer","title":"Rows"},"width":{"type":"integer","title":"Width","description":"Per-cell width in px."},"window":{"$ref":"#/components/schemas/PreviewWindow","description":"Timeline range the sheet spans."},"cells":{"items":{"$ref":"#/components/schemas/ContactSheetCell"},"type":"array","title":"Cells"}},"type":"object","required":["columns","rows","width","window"],"title":"ContactSheetPlan","description":"The sampling plan: which frames the sheet will show, and the grid shape.\n\nReturned immediately (even on ``dry_run``), so an agent gets the composition-\naware sample plan for free before any render."},"CornerRadii":{"properties":{"top_left":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Top Left","default":0},"top_right":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Top Right","default":0},"bottom_right":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Bottom Right","default":0},"bottom_left":{"type":"number","maximum":1.0,"minimum":0.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)."},"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)."},"CursorPage_ApiKeyOut_":{"properties":{"data":{"items":{"$ref":"#/components/schemas/ApiKeyOut"},"type":"array","title":"Data"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"has_more":{"type":"boolean","title":"Has More","default":false}},"type":"object","required":["data"],"title":"CursorPage[ApiKeyOut]"},"CursorPage_BrandKitOut_":{"properties":{"data":{"items":{"$ref":"#/components/schemas/BrandKitOut"},"type":"array","title":"Data"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"has_more":{"type":"boolean","title":"Has More","default":false}},"type":"object","required":["data"],"title":"CursorPage[BrandKitOut]"},"CursorPage_JobOut_":{"properties":{"data":{"items":{"$ref":"#/components/schemas/JobOut"},"type":"array","title":"Data"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"has_more":{"type":"boolean","title":"Has More","default":false}},"type":"object","required":["data"],"title":"CursorPage[JobOut]"},"CursorPage_MediaAssetOut_":{"properties":{"data":{"items":{"$ref":"#/components/schemas/MediaAssetOut"},"type":"array","title":"Data"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"has_more":{"type":"boolean","title":"Has More","default":false}},"type":"object","required":["data"],"title":"CursorPage[MediaAssetOut]"},"CursorPage_ProjectOut_":{"properties":{"data":{"items":{"$ref":"#/components/schemas/ProjectOut"},"type":"array","title":"Data"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"has_more":{"type":"boolean","title":"Has More","default":false}},"type":"object","required":["data"],"title":"CursorPage[ProjectOut]"},"CursorPage_ShaderOut_":{"properties":{"data":{"items":{"$ref":"#/components/schemas/ShaderOut"},"type":"array","title":"Data"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"has_more":{"type":"boolean","title":"Has More","default":false}},"type":"object","required":["data"],"title":"CursorPage[ShaderOut]"},"CursorPage_TemplateOut_":{"properties":{"data":{"items":{"$ref":"#/components/schemas/TemplateOut"},"type":"array","title":"Data"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"has_more":{"type":"boolean","title":"Has More","default":false}},"type":"object","required":["data"],"title":"CursorPage[TemplateOut]"},"CursorPage_WebhookDeliveryOut_":{"properties":{"data":{"items":{"$ref":"#/components/schemas/WebhookDeliveryOut"},"type":"array","title":"Data"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"has_more":{"type":"boolean","title":"Has More","default":false}},"type":"object","required":["data"],"title":"CursorPage[WebhookDeliveryOut]"},"CursorPage_WebhookOut_":{"properties":{"data":{"items":{"$ref":"#/components/schemas/WebhookOut"},"type":"array","title":"Data"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"has_more":{"type":"boolean","title":"Has More","default":false}},"type":"object","required":["data"],"title":"CursorPage[WebhookOut]"},"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)."},"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).\n\n`element` entries are bindable from any video/image/shape/group\nanimation via `motion[].custom` (or `animations[].custom` on groups).\n`text` entries bind from a text element's `animation_preset` or\n`motion[].custom`.\n\nNames that shadow a catalog preset — or the reserved static keys `none`/\n`overlay`/`difference` — are rejected here: the engine warn-and-*ignores*\nsuch a definition (renderer_node.cpp:1500-1516) and the reference then\nsilently resolves to the catalog preset instead of yours."},"DashOffsetKeyframe":{"properties":{"time":{"type":"number","minimum":0.0,"title":"Time","description":"Timeline seconds."},"offset":{"type":"number","title":"Offset","description":"Dash offset at this time, in viewBox units (SVG `stroke-dashoffset`)."},"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","offset"],"title":"DashOffsetKeyframe","description":"A keyframe on the dash offset (marching ants).\n\n``offset`` is the whole SVG ``stroke-dashoffset`` at ``time`` in the same\nviewBox units as the static ``stroke_dash_offset`` — nothing inherits on\nthis channel, there is one value to animate."},"DesignSize":{"properties":{"width":{"type":"number","exclusiveMinimum":0.0,"title":"Width","description":"Width in design px.","example":1920},"height":{"type":"number","exclusiveMinimum":0.0,"title":"Height","description":"Height in design px.","example":1080}},"additionalProperties":false,"type":"object","required":["width","height"],"title":"DesignSize","description":"The logical root (render-node AEC §6): the box frames and instances at the root are\nplaced in, in design px, scaled uniformly to the output, whose aspect it must have."},"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.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.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.\n\n``distance`` and ``blur`` are the engine's unitless 0–100 strength scale,\nNOT pixels — a px-looking ``distance: 10`` is a fraction of what you meant\non a small element and renders as nothing at all. The offset lands at\n``distance × 0.0005 × the element's shorter side``; the reference scenes\nauthor 60–70 for both. For pixel-unit shadows use ``shadow_x``/``shadow_y``\non video and image elements. The layout lint reports a distance that\nrenders under a pixel as ``SHADOW_BELOW_MIN_SIZE``."},"EaseBack":{"properties":{"back":{"$ref":"#/components/schemas/BackEasing"}},"additionalProperties":false,"type":"object","required":["back"],"title":"EaseBack","description":"An anticipate or overshoot ease (AE 'back')."},"EaseBezier":{"properties":{"bezier":{"items":{"type":"number"},"type":"array","maxItems":4,"minItems":4,"title":"Bezier","description":"`[x1, y1, x2, y2]`, as CSS `cubic-bezier`."}},"additionalProperties":false,"type":"object","required":["bezier"],"title":"EaseBezier","description":"A CSS cubic-bezier curve."},"EaseElastic":{"properties":{"elastic":{"$ref":"#/components/schemas/ElasticEasing"}},"additionalProperties":false,"type":"object","required":["elastic"],"title":"EaseElastic","description":"An elastic ring-in or ring-out ease."},"EaseFamily":{"properties":{"family":{"type":"string","enum":["smooth","natural","slowdown","accelerate","overshoot","anticipate","anticipate_overshoot","spring","elastic","bounce","impulse","throw"],"title":"Family","description":"The curve's character."},"intensity":{"type":"number","maximum":100.0,"minimum":0.0,"title":"Intensity","description":"How strongly, 0 to 100.","example":65}},"additionalProperties":false,"type":"object","required":["family","intensity"],"title":"EaseFamily","description":"One of the engine's twelve easing families at an intensity (GVR M3)."},"EaseSpring":{"properties":{"spring":{"$ref":"#/components/schemas/SpringEasing"}},"additionalProperties":false,"type":"object","required":["spring"],"title":"EaseSpring","description":"A damped spring over the window."},"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`).\n\nEvery name here resolves on every keyframe surface (morph/trim/mask/custom\nanimation keyframes, selector ``ease``, counter ``easing``). ``ease_in`` /\n``ease_out`` / ``ease_in_out`` are the AE/CSS aliases of the ``quad_*``\ncurves. ``square_*`` is a **step** family (``square_in_out`` snaps at the\nmidpoint), not a power curve — the quadratic curves are ``quad_*``.\n``hold`` freezes the prior value until the next keyframe.\n\nThe parameterized families (spring / back / elastic / bezier) are not\nnames: express them through the dedicated keyframe sub-objects.\n\nValues:\n- `linear` — Constant speed, no acceleration.\n- `hold` — Freeze the previous value until the next keyframe (step).\n- `ease_in` — Starts slow, accelerates (alias of quad_in).\n- `ease_out` — Starts fast, decelerates (alias of quad_out).\n- `ease_in_out` — Slow start and end, faster in the middle (alias of quad_in_out).\n- `sin_in` — Gentle sinusoidal acceleration in.\n- `sin_out` — Gentle sinusoidal deceleration out.\n- `sin_in_out` — Gentle sinusoidal ease both ends.\n- `square_in` — Step: snaps at the segment end (not a power curve).\n- `square_out` — Step: snaps at the segment start (not a power curve).\n- `square_in_out` — Step: snaps at the midpoint — the typewriter/glitch snap.\n- `expo_in` — Extreme exponential acceleration in.\n- `expo_out` — Extreme exponential deceleration out.\n- `expo_in_out` — Extreme exponential ease both ends.\n- `quad_in` — Quadratic acceleration in.\n- `quad_out` — Quadratic deceleration out.\n- `quad_in_out` — Quadratic ease both ends.\n- `cubic_in` — Cubic acceleration in (stronger than quad).\n- `cubic_out` — Cubic deceleration out (stronger than quad).\n- `cubic_in_out` — Cubic ease both ends.\n- `quart_in` — Quartic acceleration in.\n- `quart_out` — Quartic deceleration out.\n- `quart_in_out` — Quartic ease both ends.\n- `quint_in` — Quintic acceleration in (near-expo).\n- `quint_out` — Quintic deceleration out (near-expo).\n- `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."}},"Effect":{"properties":{"type":{"anyOf":[{"$ref":"#/components/schemas/EffectType"},{"type":"null"}],"description":"The effect from the vocabulary (`effects` in GET /v1/capabilities). Omit it beside `shader`."},"intensity":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Intensity","description":"Effect strength, 0 to 100. On a shader effect it is null unless set: the shader then runs at the engine's own full intensity rather than the effect-wide 50.","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"}]},"shader":{"anyOf":[{"$ref":"#/components/schemas/EffectShader"},{"type":"null"}],"description":"A runtime shader in place of `type` (render-node AEC §2): `{id, params}`, with `intensity` beside it and nothing else. Authoring: `shaders` in GET /v1/capabilities."}},"additionalProperties":false,"type":"object","title":"Effect"},"EffectShader":{"properties":{"id":{"type":"string","minLength":1,"title":"Id","description":"The shader's id (`sh_...`): one of this workspace's or a shared one (GET /v1/shaders).","example":"sh_01J8QR2K5VKDGN2T4FBM3CZYX7"},"params":{"additionalProperties":{"anyOf":[{"type":"number"},{"type":"string"},{"items":{"type":"number"},"type":"array","maxItems":2,"minItems":2},{"items":{"$ref":"#/components/schemas/ShaderParamKeyframe"},"type":"array","minItems":1}]},"type":"object","title":"Params","description":"Values of the shader's declared params by name: a number (`float`), \"#RRGGBBAA\" (`color`) or [x, y] (`vec2`), or keyframes `[{time, value}]` in timeline seconds. A param left out takes its declared default.","default":{}}},"additionalProperties":false,"type":"object","required":["id"],"title":"EffectShader","description":"A runtime shader (render-node AEC §2) by its stored id. `POST /v1/shaders` compiles one\nfrom SkSL source; the render carries its compiled variants, so it renders on the farm and\nin the preview alike."},"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:\n- `vintage` — Faded, warm retro film look with lifted blacks.\n- `polaroid` — Instant-photo look — soft contrast, warm cast, subtle vignette.\n- `portra` — Kodak Portra-style film emulation with natural skin tones.\n- `super8` — Super-8 home-movie look — grain, gate weave, warm color.\n- `filmic` — Cinematic film grade with rolled-off highlights and rich contrast.\n- `add_grain` — Adds dusty analog film grain over the image.\n- `rgb_split` — Offsets the red/green/blue channels for a glitchy chromatic split.\n- `ghosting` — Trailing motion echoes, like persistence-of-vision smearing.\n- `displacement_map` — Warps the image with a displacement texture for a melted look.\n- `posterize` — Reduces the image to a small number of flat color bands.\n- `mosaic` — Pixelates the image into large mosaic blocks.\n- `mosaic_blur` — Pixelated mosaic with softened, blurred block edges.\n- `mosaic_posterize` — Combines mosaic pixelation with posterized colors.\n- `cc_halftone` — Comic-style halftone dot pattern.\n- `cc_halftone_blue` — Halftone dot pattern tinted blue.\n- `cc_halftone_green` — Halftone dot pattern tinted green.\n- `cc_halftone_red` — Halftone dot pattern tinted red.\n- `invert` — Inverts all colors (photo-negative).\n- `optics_compensation` — Fish-eye / lens-distortion warp.\n- `viewfinder` — Camera viewfinder overlay (REC indicator, framing marks).\n- `night_vision` — Green night-vision look with glow and noise.\n- `thermal` — Thermal-camera false-color heat map.\n- `camera_lens_blur` — Realistic bokeh lens blur across the frame.\n- `camera_lens_blur_bg` — Bokeh lens blur applied to the background only.\n- `box_blur` — Fast, uniform box blur.\n- `lens_flare` — Adds an anamorphic lens-flare streak and glints.\n- `strobe_light` — Rapid brightness strobing / flashing.\n- `snow` — Falling-snow particle overlay.\n- `glow` — Dreamy soft-glow bloom over highlights.\n- `sepia` — Sepia-toned monochrome with a vignette.\n- `echo` — Soft ghosted dream-echo overlay.\n- `chromatic_aberration` — Color-fringing at edges, like cheap-lens dispersion.\n- `vhs` — VHS tape look: tracking noise, color bleed, and wobble.\n- `vhs_overlay` — VHS noise/scanline layer composited over the source.\n- `crt` — CRT monitor look: screen curvature, scanlines, and phosphor glow.\n- `television` — Analog TV look: scanlines, static, and signal distortion.\n- `glitch` — Digital glitch: block displacement and channel tearing.\n- `compression_glitch` — MPEG compression artifacts: blocky macroblocks and datamosh.\n- `scanlines` — Horizontal scanlines overlaid on the image.\n- `prism` — Prismatic light refraction with a rainbow color split.\n- `light_leaks` — Warm light-leak streaks bleeding across the frame.\n- `film_burn` — Burning-film look with scorched edges and flares.\n- `duotone` — Two-tone color map (shadows to one hue, highlights to another).\n- `cross_process` — Cross-processed film look with shifted, punchy colors.\n- `bleach_bypass` — High-contrast, desaturated bleach-bypass film grade.\n- `chroma_key` — Keys out a color (green/blue screen); tune via `chroma_settings`.\n- `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."}},"ElasticEasing":{"properties":{"amplitude":{"type":"number","title":"Amplitude","description":"Ring amplitude.","default":1.0},"period":{"type":"number","exclusiveMinimum":0.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."},"EnumValueCap":{"properties":{"value":{"type":"string","title":"Value","description":"The enum value to send in a request."},"description":{"type":"string","title":"Description","description":"What this value does."},"supported":{"type":"boolean","title":"Supported","description":"Whether the renderer honors this value today."}},"type":"object","required":["value","description","supported"],"title":"EnumValueCap"},"ErrorCode":{"type":"string","enum":["invalid_source","source_not_found","source_too_large","asset_not_ready","codec_unsupported","decode_failed","encode_failed","timeout","out_of_memory","gpu_error","upload_failed","provider_error","ingest_failed","render_refused","internal","cancelled"],"title":"ErrorCode","description":"Structured error codes surfaced on Job.error_code.\n\nValues:\n- `invalid_source` — A source URL was malformed or inaccessible.\n- `source_not_found` — A source URL returned 404.\n- `source_too_large` — A source file exceeded the maximum allowed size.\n- `asset_not_ready` — A referenced asset was still ingesting when the job ran.\n- `codec_unsupported` — A source used a codec the renderer cannot decode.\n- `decode_failed` — A source file could not be decoded.\n- `encode_failed` — The output could not be encoded.\n- `timeout` — Processing exceeded the time limit.\n- `out_of_memory` — The renderer ran out of GPU memory.\n- `gpu_error` — A GPU hardware/driver error occurred.\n- `upload_failed` — The output artifact failed to upload to storage.\n- `provider_error` — An upstream provider (e.g. AssemblyAI) returned an error.\n- `ingest_failed` — An external source could not be probed (unreachable, not a media file, or timed out).\n- `render_refused` — The renderer refused the composition by name: a runtime shader that did not compile or ran over its GPU time budget, or an operation, a component or a nested composition it could not apply. `error_message` carries the engine's words; change what it names and submit again.\n- `internal` — An unexpected internal error occurred.\n- `cancelled` — The job was cancelled.","x-enumDescriptions":{"invalid_source":"A source URL was malformed or inaccessible.","source_not_found":"A source URL returned 404.","source_too_large":"A source file exceeded the maximum allowed size.","asset_not_ready":"A referenced asset was still ingesting when the job ran.","codec_unsupported":"A source used a codec the renderer cannot decode.","decode_failed":"A source file could not be decoded.","encode_failed":"The output could not be encoded.","timeout":"Processing exceeded the time limit.","out_of_memory":"The renderer ran out of GPU memory.","gpu_error":"A GPU hardware/driver error occurred.","upload_failed":"The output artifact failed to upload to storage.","provider_error":"An upstream provider (e.g. AssemblyAI) returned an error.","ingest_failed":"An external source could not be probed (unreachable, not a media file, or timed out).","render_refused":"The renderer refused the composition by name: a runtime shader that did not compile or ran over its GPU time budget, or an operation, a component or a nested composition it could not apply. `error_message` carries the engine's words; change what it names and submit again.","internal":"An unexpected internal error occurred.","cancelled":"The job was cancelled."}},"Fonts":{"properties":{"heading":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Heading","description":"Display / heading family."},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body","description":"Body-copy family."}},"type":"object","title":"Fonts","description":"Typography — Google-family names (unvalidated; renderer resolves them)."},"FrameElement":{"properties":{"x":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"X","description":"Left edge in design px from its parent's left edge (at the root, the `design_size` box's, else the output's). Omitted: 0."},"y":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Y","description":"Top edge in design px from its parent's top edge. Omitted: 0."},"x_anchor":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"string"},{"type":"null"}],"title":"X Anchor","description":"Pivot it turns and scales about, across its own box: a percent, or design px from its left edge. Omitted: the centre."},"y_anchor":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"string"},{"type":"null"}],"title":"Y Anchor","description":"Pivot across its own box's height: a percent, or design px from its top edge. Omitted: the centre."},"z_rotation":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"string"},{"type":"null"}],"title":"Z Rotation","description":"Rotation in degrees. Omitted: 0."},"x_rotation":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"string"},{"type":"null"}],"title":"X Rotation","description":"Perspective tilt (X axis) each element inside takes about its own centre, as a group's does. Omitted: 0."},"y_rotation":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"string"},{"type":"null"}],"title":"Y Rotation","description":"Perspective tilt (Y axis); see `x_rotation`. Omitted: 0."},"scale":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Scale","description":"Uniform scale about the anchor. Omitted: 1."},"opacity":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Opacity","description":"Opacity 0 to 100 of everything inside. Omitted: 100."},"z_index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Z Index","description":"Stacking order among its siblings (higher on top); ties draw in array order. Everything inside draws together at its place."},"duration":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Duration","description":"Seconds active. Omitted: until its parent ends."},"motion":{"items":{"$ref":"#/components/schemas/Motion"},"type":"array","title":"Motion","description":"Animations of the whole, about its anchor.","default":[]},"clip":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Clip","description":"Crop what is inside to its box."},"corner_radius":{"anyOf":[{"type":"number","minimum":0.0},{"items":{"type":"number","minimum":0.0},"type":"array","maxItems":4,"minItems":4},{"$ref":"#/components/schemas/GeometryCornerRadii"},{"type":"null"}],"title":"Corner Radius","description":"Corner radius of the clip in design px: a number, `[top_left, top_right, bottom_right, bottom_left]` or `{top_left, ...}`. Applies with `clip`."},"blend_mode":{"anyOf":[{"$ref":"#/components/schemas/BlendMode"},{"type":"null"}],"description":"Blend of its content over what is below. Omitted: normal."},"mask_shape":{"anyOf":[{"$ref":"#/components/schemas/MaskShape"},{"type":"null"}],"description":"Analytic mask over its box."},"mask":{"anyOf":[{"$ref":"#/components/schemas/MaskConfig"},{"type":"null"}],"description":"Feather, scale and centre of `mask_shape`."},"matte":{"anyOf":[{"$ref":"#/components/schemas/Matte"},{"type":"null"}],"description":"Track matte: another element's render drives its coverage."},"effects":{"items":{"$ref":"#/components/schemas/Effect"},"type":"array","title":"Effects","description":"Shader effects run over its content.","default":[]},"render":{"anyOf":[{"type":"string","enum":["flatten","composite"]},{"type":"null"}],"title":"Render","description":"`composite` makes it a nested composition whose content runs on `time_map`'s clock; its content is graphics only (no video). Omitted: `flatten`."},"time_map":{"anyOf":[{"$ref":"#/components/schemas/TimeMap"},{"type":"null"}],"description":"A composite's clock: `offset`, `speed`, `loop` and `freeze`. Needs `render: \"composite\"`."},"type":{"type":"string","const":"frame","title":"Type","default":"frame"},"id":{"type":"string","minLength":1,"title":"Id","description":"**Required.** Unique in the request; operations, mattes and anchors name it.","example":"card"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"Optional human-readable label. Not used by the renderer."},"track":{"anyOf":[{"type":"integer","maximum":255.0,"minimum":0.0},{"type":"null"}],"title":"Track","description":"Timeline track index (0–255). Informational only; not used by the renderer."},"time":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Time","description":"Start in seconds on its parent's clock (the composition's at the root). Omitted: its parent's start."},"visible":{"type":"boolean","title":"Visible","description":"Set to `false` to skip this element without removing it from the request.","default":true},"locked":{"type":"boolean","title":"Locked","description":"Marks the element locked in editors. Not sent to the renderer.","default":false},"width":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Width","description":"Width in design px. Omitted: its parent's width."},"height":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Height","description":"Height in design px. Omitted: its parent's height."},"slot":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Slot","description":"In a component body: the slot name an instance's `slots` fill in place of this frame's own children."},"children":{"items":{"oneOf":[{"$ref":"#/components/schemas/ChildVideoElement"},{"$ref":"#/components/schemas/ChildTextElement"},{"$ref":"#/components/schemas/ChildImageElement"},{"$ref":"#/components/schemas/ChildShapeElement"},{"$ref":"#/components/schemas/FrameElement"},{"$ref":"#/components/schemas/InstanceElement"}],"discriminator":{"propertyName":"type","mapping":{"frame":"#/components/schemas/FrameElement","image":"#/components/schemas/ChildImageElement","instance":"#/components/schemas/InstanceElement","shape":"#/components/schemas/ChildShapeElement","text":"#/components/schemas/ChildTextElement","video":"#/components/schemas/ChildVideoElement"}}},"type":"array","title":"Children","description":"The elements inside, in design px from this frame's top-left corner; frames and instances nest. Audio stays at the root.","default":[]}},"additionalProperties":false,"type":"object","required":["id"],"title":"FrameElement","description":"A frame (render-node AEC §5): elements nested in its box, with its own clip, mask,\nmatte, blend, effects and, as a composite, its own clock. Its children use design px\nfrom its top-left corner, frames and instances included."},"GeometryCornerRadii":{"properties":{"top_left":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Top Left"},"top_right":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Top Right"},"bottom_right":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Bottom Right"},"bottom_left":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Bottom Left"}},"additionalProperties":false,"type":"object","title":"GeometryCornerRadii","description":"Per-corner radii in px; a corner left out is 0."},"GeometryEllipse":{"properties":{},"additionalProperties":false,"type":"object","title":"GeometryEllipse","description":"The ellipse inscribed in the element box (a circle when the box is square)."},"GeometryLine":{"properties":{"x1":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"X1"},"y1":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Y1"},"x2":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"X2"},"y2":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Y2"}},"additionalProperties":false,"type":"object","title":"GeometryLine","description":"A straight line painted with the shape's `stroke` (else its `fill`).\n\nEndpoints are px from the element box's top-left corner; the width is\n`stroke_width` and the ends are `stroke_linecap`. Endpoints left out give\nthe box's horizontal centre line."},"GeometryPolygon":{"properties":{"sides":{"anyOf":[{"type":"integer","maximum":60.0,"minimum":3.0},{"type":"null"}],"title":"Sides","description":"Vertices. Omitted: 5."},"corner_radius":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Corner Radius","description":"Rounds every vertex with an arc of this radius in px. Omitted: 0."}},"additionalProperties":false,"type":"object","title":"GeometryPolygon","description":"A regular polygon inscribed in the ellipse the box holds, its first vertex at 12\no'clock."},"GeometryRect":{"properties":{"corner_radius":{"anyOf":[{"type":"number","minimum":0.0},{"items":{"type":"number","minimum":0.0},"type":"array","maxItems":4,"minItems":4},{"$ref":"#/components/schemas/GeometryCornerRadii"},{"type":"null"}],"title":"Corner Radius","description":"Corner radius in px: one number for all four corners, `[top_left, top_right, bottom_right, bottom_left]`, or `{top_left, top_right, bottom_right, bottom_left}` (missing corners 0). Omitted: square corners."},"smoothing":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Smoothing","description":"Figma's corner smoothing, 0 to 1: above 0 the corners ease into the edges (a squircle at 1). Omitted: 0, the plain rounded rect."}},"additionalProperties":false,"type":"object","title":"GeometryRect","description":"A rectangle filling the element box, optionally with rounded corners."},"GeometryRing":{"properties":{"inner_ratio":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Inner Ratio","description":"The hole's share of the radius, 0–1 (0 is a pie). Omitted: 0."},"inner_ratio_percent":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Inner Ratio Percent","description":"`inner_ratio` as a percent (85 is 0.85)."},"start_angle":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Start Angle","description":"Where the sweep starts, degrees, 0 at 3 o'clock, clockwise. Omitted: 0."},"sweep":{"anyOf":[{"type":"number","maximum":360.0,"minimum":0.0},{"type":"null"}],"title":"Sweep","description":"Degrees drawn clockwise from `start_angle`. Omitted: 360. With `segments` the sweep is theirs, so leave it out."},"sweep_percent":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Sweep Percent","description":"`sweep` as a percent of the full circle (100 = 360°)."},"cap":{"anyOf":[{"type":"string","enum":["butt","round"]},{"type":"null"}],"title":"Cap","description":"Ends of a partial sweep. Omitted: `butt`."},"segments":{"anyOf":[{"items":{"$ref":"#/components/schemas/GeometrySegment"},"type":"array","maxItems":32,"minItems":1},{"type":"null"}],"title":"Segments","description":"Consecutive solid slices (at most 32, together at most 360°) drawn as one shape, so abutting slices leave no seam."}},"additionalProperties":false,"type":"object","title":"GeometryRing","description":"A ring, arc, pie or segmented ring: circular, inscribed in the box's shorter side."},"GeometrySegment":{"properties":{"sweep":{"anyOf":[{"type":"number","maximum":360.0,"minimum":0.0},{"type":"null"}],"title":"Sweep","description":"Slice angle in degrees."},"sweep_percent":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Sweep Percent","description":"Slice as a percent of the full circle (100 = 360°)."},"fill":{"type":"string","pattern":"^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","title":"Fill","description":"The slice's solid colour.","example":"#2a85ff"}},"additionalProperties":false,"type":"object","required":["fill"],"title":"GeometrySegment","description":"One solid-coloured slice of a segmented ring, drawn after the previous one."},"GeometryShadow":{"properties":{"type":{"anyOf":[{"type":"string","enum":["drop","inner"]},{"type":"null"}],"title":"Type","description":"Omitted: `drop`."},"x":{"anyOf":[{"type":"number","maximum":100000.0,"minimum":-100000.0},{"type":"null"}],"title":"X","description":"Horizontal offset in px."},"y":{"anyOf":[{"type":"number","maximum":100000.0,"minimum":-100000.0},{"type":"null"}],"title":"Y","description":"Vertical offset in px, down positive."},"blur":{"anyOf":[{"type":"number","maximum":100000.0,"minimum":0.0},{"type":"null"}],"title":"Blur","description":"CSS blur radius in px (sigma = blur / 2)."},"spread":{"anyOf":[{"type":"number","maximum":100000.0,"minimum":-100000.0},{"type":"null"}],"title":"Spread","description":"Spread in px."},"color":{"type":"string","pattern":"^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","title":"Color","example":"#00000066"}},"additionalProperties":false,"type":"object","required":["color"],"title":"GeometryShadow","description":"A CSS-style box shadow on a rect or circle geometry, in px, each length within\n+-100000."},"GeometryStar":{"properties":{"points":{"anyOf":[{"type":"integer","maximum":60.0,"minimum":3.0},{"type":"null"}],"title":"Points","description":"Outer vertices. Omitted: 5."},"inner_ratio":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Inner Ratio","description":"The inner vertices' share of the outer ellipse. Omitted: 0.382, a regular pentagram's."},"corner_radius":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Corner Radius","description":"Rounds every vertex with an arc of this radius in px. Omitted: 0."}},"additionalProperties":false,"type":"object","title":"GeometryStar","description":"A star: `points` outer vertices on the ellipse the box holds (the first at 12\no'clock), alternating with inner ones on that ellipse scaled by `inner_ratio`."},"Glass":{"properties":{"bevel":{"anyOf":[{"type":"number","maximum":256.0,"minimum":0.0},{"type":"null"}],"title":"Bevel","description":"Width of the bent band at the edge in canvas px, at most 256. Omitted: 16."},"refraction":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Refraction","description":"How far the edge bends the scene inward, 0–1. Omitted: 0.5."},"dispersion":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Dispersion","description":"How far red and blue split apart at the edge, 0–1. Omitted: 0."}},"additionalProperties":false,"type":"object","title":"Glass","description":"A refracting glass surface (V6, render-node AEC §1): within `bevel` px of the\nelement's edge (a shape's own outline, a box's rounded corners) the scene behind is\nread inward, bent by `refraction`, its red and blue split by `dispersion`. With\n`backdrop_blur` it bends the blurred scene, without it the sharp one."},"Gradient":{"properties":{"kind":{"type":"string","enum":["linear","radial","conic"],"title":"Kind","description":"`conic` sweeps the stops clockwise once round `center`, starting at `angle_degrees`.","default":"linear"},"angle_degrees":{"type":"number","title":"Angle Degrees","description":"Linear gradient angle in degrees, measured clockwise on the screen (y down): 0 = left→right (offset 0 at the left), 90 = top→bottom, 45 = top-left→bottom-right, 315 = bottom-left→top-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. On a conic gradient: where the sweep starts, degrees clockwise from 3 o'clock.","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 down the screen, as CSS `at` does: `{x: 0.5, y: 0}` is the top edge and `{x: 0.5, y: 1}` the bottom (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. A conic gradient sweeps round this point."},"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"},"GradientStop":{"properties":{"offset":{"type":"number","maximum":1.0,"minimum":0.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"},"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.0,"title":"Scale","description":"Group scale.","default":1.0},"rotation_degrees":{"type":"number","title":"Rotation Degrees","description":"In-plane (z) rotation of the group.","default":0.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.0,"minimum":0.0,"title":"Opacity","description":"Group opacity.","default":1.0},"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.\n\nThe alias of a frame (render-node AEC §5, TR1): the engine lowers each group to a frame\nwhose box is the canvas, anchored at `pivot`, offset by `translate`, with `members` as\nits children, so nothing it draws changes. Kept for existing documents; nest elements\nin a `frame` element instead. A request with `frame` or `instance` elements carries no\ngroups."},"HealthResponse":{"properties":{"status":{"type":"string","title":"Status","default":"ok"}},"type":"object","title":"HealthResponse"},"ImageElement":{"properties":{"lut_url":{"anyOf":[{"type":"string","maxLength":2083,"minLength":1,"format":"uri"},{"type":"null"}],"title":"Lut Url"},"lut_intensity":{"type":"number","maximum":100.0,"minimum":0.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.0,"minimum":0.0,"title":"Crop Top","default":0},"crop_bottom":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Crop Bottom","default":0},"crop_left":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Crop Left","default":0},"crop_right":{"type":"number","maximum":1.0,"minimum":0.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","description":"Glass: the scene behind the element, Gaussian-blurred, fills its footprint under its translucent content (a shape's own silhouette, a video's or image's box with its corner radius and mask). A Gaussian sigma in canvas px, as CSS `backdrop-filter: blur()` takes it; the renderer caps it at 128. 0 is off.","default":0},"backdrop_blur_keyframes":{"anyOf":[{"items":{"$ref":"#/components/schemas/BlurKeyframe"},"type":"array","minItems":1},{"type":"null"}],"title":"Backdrop Blur Keyframes","description":"Animate `backdrop_blur` over the timeline; replaces the static value while present."},"layer_blur":{"type":"number","maximum":128.0,"minimum":0.0,"title":"Layer Blur","description":"Blurs the element itself as drawn: a Gaussian sigma in canvas px, as CSS `filter: blur()` takes it, spreading three sigma past its box. At most 128; 0 is off. Not on a shape yet (the editor's preview cannot draw it), and not with `blend_mode`, `backdrop_blur` or `matte`, beside which the renderer would draw the element without it.","default":0},"layer_blur_keyframes":{"anyOf":[{"items":{"$ref":"#/components/schemas/BlurKeyframe"},"type":"array","minItems":1},{"type":"null"}],"title":"Layer Blur Keyframes","description":"Animate `layer_blur` over the timeline; replaces the static value while present."},"glass":{"anyOf":[{"$ref":"#/components/schemas/Glass"},{"type":"null"}],"description":"A refracting glass edge: the scene behind bends inward near the element's outline. Pair it with `backdrop_blur` for frosted glass."},"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.0,"minimum":0.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.0,"minimum":0.0},{"type":"null"}],"title":"Track","description":"Timeline track index (0–255). Informational only; not used by the renderer."},"time":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Time","description":"When this element starts on the output timeline, in seconds. Defaults to `0` when omitted.","example":0.0},"visible":{"type":"boolean","title":"Visible","description":"Set to `false` to skip this element without removing it from the request.","default":true},"locked":{"type":"boolean","title":"Locked","description":"Marks the element locked in editors. Not sent to the renderer.","default":false},"duration":{"type":"number","exclusiveMinimum":0.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.0},"source_url":{"type":"string","maxLength":2083,"minLength":1,"format":"uri","title":"Source Url","description":"URL of the image file (PNG, JPG, WebP, GIF, SVG). A `.svg` draws a subset: groups and the basic shapes and paths, filled and stroked with flat colours. An element that uses anything else (a `style` attribute, `clip-path`, `mask`, `filter`, `stroke-dasharray`, a gradient or `currentColor` paint, `<text>`, `<use>`, `<image>`) is not drawn, nor anything inside it; an SVG that would draw nothing at all is refused at submit. Set explicit colours and presentation attributes, or pre-rasterize 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"},"InputFormatCap":{"properties":{"extension":{"type":"string","title":"Extension","description":"Canonical file extension (no leading dot)."},"mime_type":{"type":"string","title":"Mime Type","description":"Accepted MIME type for uploads/ingest."},"kind":{"type":"string","title":"Kind","description":"Media category: video, audio, image, or font."}},"type":"object","required":["extension","mime_type","kind"],"title":"InputFormatCap"},"InstanceElement":{"properties":{"x":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"X","description":"Left edge in design px from its parent's left edge (at the root, the `design_size` box's, else the output's). Omitted: 0."},"y":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Y","description":"Top edge in design px from its parent's top edge. Omitted: 0."},"x_anchor":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"string"},{"type":"null"}],"title":"X Anchor","description":"Pivot it turns and scales about, across its own box: a percent, or design px from its left edge. Omitted: the centre."},"y_anchor":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"string"},{"type":"null"}],"title":"Y Anchor","description":"Pivot across its own box's height: a percent, or design px from its top edge. Omitted: the centre."},"z_rotation":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"string"},{"type":"null"}],"title":"Z Rotation","description":"Rotation in degrees. Omitted: 0."},"x_rotation":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"string"},{"type":"null"}],"title":"X Rotation","description":"Perspective tilt (X axis) each element inside takes about its own centre, as a group's does. Omitted: 0."},"y_rotation":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"string"},{"type":"null"}],"title":"Y Rotation","description":"Perspective tilt (Y axis); see `x_rotation`. Omitted: 0."},"scale":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Scale","description":"Uniform scale about the anchor. Omitted: 1."},"opacity":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Opacity","description":"Opacity 0 to 100 of everything inside. Omitted: 100."},"z_index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Z Index","description":"Stacking order among its siblings (higher on top); ties draw in array order. Everything inside draws together at its place."},"duration":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Duration","description":"Seconds active. Omitted: until its parent ends."},"motion":{"items":{"$ref":"#/components/schemas/Motion"},"type":"array","title":"Motion","description":"Animations of the whole, about its anchor.","default":[]},"clip":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Clip","description":"Crop what is inside to its box."},"corner_radius":{"anyOf":[{"type":"number","minimum":0.0},{"items":{"type":"number","minimum":0.0},"type":"array","maxItems":4,"minItems":4},{"$ref":"#/components/schemas/GeometryCornerRadii"},{"type":"null"}],"title":"Corner Radius","description":"Corner radius of the clip in design px: a number, `[top_left, top_right, bottom_right, bottom_left]` or `{top_left, ...}`. Applies with `clip`."},"blend_mode":{"anyOf":[{"$ref":"#/components/schemas/BlendMode"},{"type":"null"}],"description":"Blend of its content over what is below. Omitted: normal."},"mask_shape":{"anyOf":[{"$ref":"#/components/schemas/MaskShape"},{"type":"null"}],"description":"Analytic mask over its box."},"mask":{"anyOf":[{"$ref":"#/components/schemas/MaskConfig"},{"type":"null"}],"description":"Feather, scale and centre of `mask_shape`."},"matte":{"anyOf":[{"$ref":"#/components/schemas/Matte"},{"type":"null"}],"description":"Track matte: another element's render drives its coverage."},"effects":{"items":{"$ref":"#/components/schemas/Effect"},"type":"array","title":"Effects","description":"Shader effects run over its content.","default":[]},"render":{"anyOf":[{"type":"string","enum":["flatten","composite"]},{"type":"null"}],"title":"Render","description":"`composite` makes it a nested composition whose content runs on `time_map`'s clock; its content is graphics only (no video). Omitted: `flatten`."},"time_map":{"anyOf":[{"$ref":"#/components/schemas/TimeMap"},{"type":"null"}],"description":"A composite's clock: `offset`, `speed`, `loop` and `freeze`. Needs `render: \"composite\"`."},"type":{"type":"string","const":"instance","title":"Type","default":"instance"},"id":{"type":"string","minLength":1,"title":"Id","description":"**Required.** Its content's ids become `<id>/<child id>`.","example":"card1"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"Optional human-readable label. Not used by the renderer."},"track":{"anyOf":[{"type":"integer","maximum":255.0,"minimum":0.0},{"type":"null"}],"title":"Track","description":"Timeline track index (0–255). Informational only; not used by the renderer."},"time":{"type":"number","minimum":0.0,"title":"Time","description":"**Required.** Start in seconds on its parent's clock; every time inside the component counts from it."},"visible":{"type":"boolean","title":"Visible","description":"Set to `false` to skip this element without removing it from the request.","default":true},"locked":{"type":"boolean","title":"Locked","description":"Marks the element locked in editors. Not sent to the renderer.","default":false},"component":{"type":"string","minLength":1,"title":"Component","description":"The `components` entry it places."},"variant":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Variant","description":"The variant by name. Omitted: the first."},"properties":{"additionalProperties":{"anyOf":[{"type":"boolean"},{"type":"integer"},{"type":"number"},{"type":"string"}]},"type":"object","title":"Properties","description":"Values of the component's declared properties, by name, each of its type.","default":{}},"slots":{"additionalProperties":{"items":{"oneOf":[{"$ref":"#/components/schemas/ChildVideoElement"},{"$ref":"#/components/schemas/ChildTextElement"},{"$ref":"#/components/schemas/ChildImageElement"},{"$ref":"#/components/schemas/ChildShapeElement"},{"$ref":"#/components/schemas/FrameElement"},{"$ref":"#/components/schemas/InstanceElement"}],"discriminator":{"propertyName":"type","mapping":{"frame":"#/components/schemas/FrameElement","image":"#/components/schemas/ChildImageElement","instance":"#/components/schemas/InstanceElement","shape":"#/components/schemas/ChildShapeElement","text":"#/components/schemas/ChildTextElement","video":"#/components/schemas/ChildVideoElement"}}},"type":"array"},"type":"object","title":"Slots","description":"Content for the component's slots, by slot name, in place of the slot frame's own children.","default":{}}},"additionalProperties":false,"type":"object","required":["id","time","component"],"title":"InstanceElement","description":"An instance (render-node AEC §5g): a placed copy of a `components` definition. Its box\nis the component's (or its variant's); it sets the component's properties, fills its\nslots, and places, times and composites itself as a frame does."},"IntensityKeyframe":{"properties":{"time":{"type":"number","minimum":0.0,"title":"Time","description":"Absolute timeline seconds."},"value":{"type":"number","maximum":1.0,"minimum":0.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."},"JobError":{"properties":{"code":{"$ref":"#/components/schemas/ErrorCode","description":"Machine-readable error code.","example":"invalid_source"},"message":{"type":"string","title":"Message","description":"Human-readable error description.","example":"Source URL returned 404."}},"type":"object","required":["code","message"],"title":"JobError"},"JobKind":{"type":"string","enum":["render","task"],"title":"JobKind"},"JobOut":{"properties":{"id":{"type":"string","title":"Id","description":"Unique job ID. Prefix indicates type: `render_` or `task_`.","example":"render_01J8QR2K5VKDGN2T4FBM3CZYX7"},"kind":{"$ref":"#/components/schemas/JobKind","description":"Whether this is a `render` or a `task`.","example":"render"},"is_preview":{"type":"boolean","title":"Is Preview","description":"True for a cheap preview render. Previews are not billed and are excluded from the renders list.","default":false,"example":false},"editor_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Editor Url","description":"Open the project this job was submitted from in the authenticated console. Absent for a job with no project."},"task_type":{"anyOf":[{"$ref":"#/components/schemas/TaskType"},{"type":"null"}],"description":"For `task` jobs only — the specific AI operation.","example":"transcribe"},"workspace_id":{"type":"string","title":"Workspace Id","description":"ID of the workspace that owns this job.","example":"ws_01J8QR2K5VKDGN2T4FBM3CZYX8"},"status":{"$ref":"#/components/schemas/JobStatus","description":"Current lifecycle state: `queued` → `processing` → `completed` / `failed` / `cancelled`.","example":"queued"},"progress_percent":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Progress Percent","description":"0–100 progress indicator updated by the render engine.","default":0,"example":0},"progress_stage":{"anyOf":[{"$ref":"#/components/schemas/ProgressStage"},{"type":"null"}],"description":"Current processing phase within a job.","example":"compositing"},"output":{"anyOf":[{"$ref":"#/components/schemas/JobOutput"},{"type":"null"}],"description":"Populated once `status == completed`. Contains the signed artifact URL."},"result":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Result","description":"Structured JSON result for tasks that return data rather than a file — `transcribe` returns the transcript here (`text`, `words`, `utterances`, and any requested analysis). Populated once `status == completed`; `null` for renders and for any job whose only artifact is the file at `output.url`.","example":{"language_code":"en","text":"Welcome back to the show."}},"error":{"anyOf":[{"$ref":"#/components/schemas/JobError"},{"type":"null"}],"description":"Populated when `status == failed`. Contains a structured error code."},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Caller-supplied key-value pairs echoed back on every webhook and response.","example":{"project_id":"proj_123","user_ref":"usr_abc"}},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"ISO-8601 UTC timestamp when the job was created."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"ISO-8601 UTC timestamp of the last status change."},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At","description":"ISO-8601 UTC timestamp when the job reached a terminal state."}},"type":"object","required":["id","kind","workspace_id","status","created_at","updated_at"],"title":"JobOut","description":"A render job or AI task."},"JobOutput":{"properties":{"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url","description":"CDN URL to the completed artifact.","example":"https://cdn-user.framelane.io/render_01J.../output.mp4"},"width":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Width","description":"Output video width in pixels.","example":1920},"height":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Height","description":"Output video height in pixels.","example":1080},"duration":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Duration","description":"Output duration in seconds.","example":15.3},"size_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Size Bytes","description":"Artifact file size in bytes.","example":12582912}},"type":"object","title":"JobOutput"},"JobStatus":{"type":"string","enum":["ingesting","queued","processing","completed","failed","cancelled"],"title":"JobStatus","description":"Values:\n- `ingesting` — External source files are being probed for duration before the render is queued.\n- `queued` — Accepted and waiting for a worker to pick it up.\n- `processing` — Actively rendering or processing.\n- `completed` — Finished successfully; the output artifact is available.\n- `failed` — Terminated with an error (see `error`).\n- `cancelled` — Cancelled by the caller before completion.","x-enumDescriptions":{"ingesting":"External source files are being probed for duration before the render is queued.","queued":"Accepted and waiting for a worker to pick it up.","processing":"Actively rendering or processing.","completed":"Finished successfully; the output artifact is available.","failed":"Terminated with an error (see `error`).","cancelled":"Cancelled by the caller before completion."}},"Limits":{"properties":{"canvas_min":{"type":"integer","title":"Canvas Min","description":"Minimum output width/height in pixels."},"canvas_max":{"type":"integer","title":"Canvas Max","description":"Maximum output width/height in pixels."},"frame_rate_min":{"type":"integer","title":"Frame Rate Min","description":"Minimum frames per second."},"frame_rate_max":{"type":"integer","title":"Frame Rate Max","description":"Maximum frames per second."},"frame_rate_default":{"type":"integer","title":"Frame Rate Default","description":"Frame rate used when omitted."},"max_source_bytes":{"type":"integer","title":"Max Source Bytes","description":"Maximum size of a single source/upload file."},"pagination_default_limit":{"type":"integer","title":"Pagination Default Limit","description":"Default page size on list endpoints."},"pagination_max_limit":{"type":"integer","title":"Pagination Max Limit","description":"Maximum page size on list endpoints."}},"type":"object","required":["canvas_min","canvas_max","frame_rate_min","frame_rate_max","frame_rate_default","max_source_bytes","pagination_default_limit","pagination_max_limit"],"title":"Limits"},"LinkUploadRequest":{"properties":{"content_type":{"type":"string","title":"Content Type","description":"MIME type of the file the person picked.","examples":["video/mp4"]},"filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filename","description":"The file's name, for display."},"duration":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Duration","description":"Browser-probed duration in seconds (video/audio)."},"width":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Width","description":"Browser-probed pixel width."},"height":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Height","description":"Browser-probed pixel height."}},"additionalProperties":false,"type":"object","required":["content_type"],"title":"LinkUploadRequest"},"MaskConfig":{"properties":{"feather":{"type":"number","minimum":0.0,"title":"Feather","description":"Soften the mask's SDF edge.","default":0},"scale":{"type":"number","exclusiveMinimum":0.0,"title":"Scale","description":"Mask scale (iris reveals).","default":1.0},"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.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."},"MaskShape":{"type":"string","enum":["circle","diamond","hexagon","star","heart","triangle"],"title":"MaskShape","description":"Analytic (SDF) shape mask that clips an element's coverage. §1.\n\nValues:\n- `circle` — Circular (ellipse) analytic mask, fit to the element bounds.\n- `diamond` — Diamond (rotated square) analytic mask.\n- `hexagon` — Regular hexagon analytic mask.\n- `star` — Five-point star analytic mask.\n- `heart` — Heart analytic mask.\n- `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."}},"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."},"MediaAssetDetailOut":{"properties":{"id":{"type":"string","title":"Id","description":"Unique asset ID.","example":"asset_01J8QR2K5VKDGN2T4FBM3CZYX7"},"source_url":{"type":"string","title":"Source Url","description":"CDN URL of the uploaded file. Pass as source_url in render requests.","example":"https://cdn-user.framelane.io/uploads/ws_01J.../a1b2c3.mp4"},"kind":{"type":"string","title":"Kind","description":"Media kind, derived from the file extension: `video`, `audio` or `image`.","example":"video"},"filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filename","description":"The file's original name, for display. Null for assets uploaded before names were recorded; derive one from `source_url` in that case.","example":"brand-logo.png"},"duration":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Duration","description":"Duration in seconds. Always null for images.","example":15.3},"width":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Width","description":"Pixel width (video or image).","example":1920},"height":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Height","description":"Pixel height (video or image).","example":1080},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the asset was uploaded."},"ready":{"type":"boolean","title":"Ready","description":"Whether this asset can be used in a render. False while a video or audio duration probe is still running."},"bytes_uploaded":{"type":"boolean","title":"Bytes Uploaded","description":"Whether the file is actually in storage. False means the upload never completed and this row is only a reservation — re-upload it. This is the one reliable confirmation for a direct upload, because those bytes go straight to storage without passing through the API."}},"type":"object","required":["id","source_url","kind","created_at","ready","bytes_uploaded"],"title":"MediaAssetDetailOut","description":"One asset by id, with the two facts a listing cannot carry.\n\nThe list route is ready-only by construction, so `ready` is implied there and\n`bytes_uploaded` is unanswerable — checking storage per row would mean a bucket\nround-trip per item. Both are worth their cost for a single asset, and without\nthem there is no way to tell a completed upload from a reserved slot."},"MediaAssetOut":{"properties":{"id":{"type":"string","title":"Id","description":"Unique asset ID.","example":"asset_01J8QR2K5VKDGN2T4FBM3CZYX7"},"source_url":{"type":"string","title":"Source Url","description":"CDN URL of the uploaded file. Pass as source_url in render requests.","example":"https://cdn-user.framelane.io/uploads/ws_01J.../a1b2c3.mp4"},"kind":{"type":"string","title":"Kind","description":"Media kind, derived from the file extension: `video`, `audio` or `image`.","example":"video"},"filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filename","description":"The file's original name, for display. Null for assets uploaded before names were recorded; derive one from `source_url` in that case.","example":"brand-logo.png"},"duration":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Duration","description":"Duration in seconds. Always null for images.","example":15.3},"width":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Width","description":"Pixel width (video or image).","example":1920},"height":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Height","description":"Pixel height (video or image).","example":1080},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the asset was uploaded."}},"type":"object","required":["id","source_url","kind","created_at"],"title":"MediaAssetOut","description":"A media asset previously uploaded to the workspace (video, audio or image)."},"Motion":{"properties":{"type":{"anyOf":[{"$ref":"#/components/schemas/MotionType"},{"type":"null"}],"description":"Catalog preset. Exactly one of `type`, `preset` or `custom` must be set."},"preset":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Preset","description":"Renderer animation name, used verbatim — any element animation on video/image/shape/group (e.g. `'pop'`, `'floatUp'`, `'out-slideBounceLeft'`, `'loop-heartBeat'`), any text animation on text (e.g. `'typewriter'`, `'roll'`, `'out-sink'`, `'loop-wavey'`). Reaches the presets `type` has no name for. The name carries its own direction: `out-*` names are exits, `loop-*` names loop with `duration` as one cycle, so `reversed` and `scope` do not apply. Exactly one of `type`, `preset` 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`, `preset` or `custom` must be set."},"time":{"type":"number","minimum":0.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.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.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.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"},"MotionBlur":{"properties":{"samples":{"type":"integer","maximum":32.0,"minimum":2.0,"title":"Samples","description":"Temporal samples per frame (2–32)."},"shutter":{"type":"number","maximum":1.0,"exclusiveMinimum":0.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).\n\nOpt-in and costly: every frame renders `samples` times, and accumulation is\nCPU-side, so the render falls back to **software encoding** — expect roughly\n`samples`× render time."},"MotionCap":{"properties":{"value":{"type":"string","title":"Value","description":"The motion preset value."},"description":{"type":"string","title":"Description","description":"What this motion does."},"supported":{"type":"boolean","title":"Supported","description":"True if any scope/direction is supported."},"element_entrance":{"type":"boolean","title":"Element Entrance","description":"Supported as an entrance animation on a non-text element (video, image, shape, group)."},"element_exit":{"type":"boolean","title":"Element Exit","description":"Supported as an exit animation on a non-text element."},"character":{"type":"boolean","title":"Character","description":"Supported as a per-glyph (character-scope) text animation."},"text_block":{"type":"boolean","title":"Text Block","description":"Supported on a text element as a whole-block animation (`scope: element`). Text and non-text elements draw from two different renderer animation sets, so a preset can be usable on one and not the other."}},"type":"object","required":["value","description","supported","element_entrance","element_exit","character","text_block"],"title":"MotionCap"},"MotionScope":{"type":"string","enum":["element","character"],"title":"MotionScope","description":"Whether a motion preset applies to the whole element or per-glyph (text only).\n\nValues:\n- `element` — The motion animates the whole element as one unit.\n- `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)."}},"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:\n- `identity` — Deliberate no-op carrier (no visible motion): attach a pure `audio` generator to any element without animating it. Element surfaces only.\n- `fade` — Fade in (entrance) or out (exit) via opacity.\n- `slide_up` — Slide in from below / out upward.\n- `slide_down` — Slide in from above / out downward.\n- `slide_left` — Slide in from the right / out to the left.\n- `slide_right` — Slide in from the left / out to the right.\n- `zoom_in` — Scale up from small to full size (entrance).\n- `zoom_out` — Scale down to small (exit).\n- `rotate_cw` — Spin in/out clockwise.\n- `rotate_ccw` — Spin in/out counter-clockwise.\n- `bounce` — Bounce in with an elastic overshoot / bounce out.\n- `wipe_left` — Reveal/conceal with a left-moving wipe.\n- `wipe_right` — Reveal/conceal with a right-moving wipe.\n- `wipe_up` — Reveal/conceal with an upward wipe.\n- `wipe_down` — Reveal/conceal with a downward wipe.\n- `ken_burns_in` — Slow zoom-in pan (Ken Burns) — entrance only.\n- `ken_burns_out` — Slow zoom-out pan (Ken Burns) — entrance only.\n- `ken_burns_in_out` — Slow zoom in then out (Ken Burns) — entrance only.\n- `loop_wiggle` — Continuous subtle wiggle (looping).\n- `loop_rotate` — Continuous stepped rotation (looping).\n- `loop_rotate_smooth` — Continuous smooth rotation (looping).\n- `loop_3d_spin` — Continuous 3D spin (looping).\n- `loop_3d_sway` — Continuous 3D sway (looping).\n- `blur` — Per-glyph soft blur-in (text entrance).\n- `evaporate` — Per-glyph rise, blur, and fade-out (text exit only).\n- `overlay` — Static Overlay blend fill (timing ignored).\n- `difference` — Static Difference blend fill (timing ignored).\n- `rubber_in` — Per-glyph rubbery overshoot as text springs in (text entrance).\n- `whip_up` — Per-glyph whip upward into place (text entrance).\n- `whip_down` — Per-glyph whip downward out of frame (text exit only).\n- `glitch_pop` — Per-glyph glitchy pop-in with jitter (text entrance).\n- `drift_in` — Per-glyph gentle drift and fade in (text entrance).\n- `drift_out` — Per-glyph gentle drift and fade out (text exit only).\n- `loop_breathe` — Per-glyph continuous breathing scale loop (text).\n- `loop_shimmer` — Per-glyph continuous shimmer loop (text).\n- `swing_in` — Element swings into place from an angle (entrance).\n- `swing_out` — Element swings away out of frame (exit only).\n- `elastic_rise` — Element rises with an elastic overshoot (entrance).\n- `elastic_drop` — Element drops away with an elastic recoil (exit only).\n- `tilt_zoom` — Element tilts and zooms into place (entrance).\n- `loop_orbit` — Element orbits continuously around its center (loop).\n- `smooth_pop` — Element pops in from 60% scale, overshooting slightly past full size before it settles, 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 from 60% scale, overshooting slightly past full size before it settles, as it fades in (entrance)."}},"MoveElementOp":{"properties":{"op":{"type":"string","const":"move_element","title":"Op"},"id":{"type":"string","title":"Id","description":"The element to move, at any depth. It keeps its id and every field."},"parent":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent","description":"The frame to move it into, or null for the root `elements`. Box values (`x`, `y`, `width`, `height`, `time`) are carried verbatim, never converted between the root's spelling (canvas percent or px, centred) and a frame's (design px from its top-left): convert them yourself, e.g. with set_fields in the same batch."},"index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Index","description":"Position among the new parent's children; appends when omitted."}},"additionalProperties":false,"type":"object","required":["op","id","parent"],"title":"MoveElementOp"},"Onboarding":{"properties":{"self_serve":{"type":"boolean","title":"Self Serve","description":"Whether an agent can provision access itself."},"summary":{"type":"string","title":"Summary","description":"One-line description of the provisioning flow."},"steps":{"items":{"$ref":"#/components/schemas/OnboardingStep"},"type":"array","title":"Steps","description":"Ordered steps to obtain a working key."},"action_mcp_url":{"type":"string","title":"Action Mcp Url","description":"Authenticated MCP action server. After obtaining a key (via the REST steps above), connect here to drive renders/tasks/projects as MCP tools."},"action_mcp_auth":{"type":"string","title":"Action Mcp Auth","description":"How to authenticate to the action MCP (Bearer key, or OAuth via Clerk)."},"docs":{"$ref":"#/components/schemas/OnboardingDocs","description":"Static documentation channels for agents."},"verification_gates_usage":{"type":"boolean","title":"Verification Gates Usage","description":"If true, an unverified workspace's key is rejected on every authed call (`403 email_not_verified`), so an agent cannot spend before verifying."},"free_plan":{"type":"string","title":"Free Plan","description":"What a newly signed-up (free) workspace can do."}},"type":"object","required":["self_serve","summary","steps","action_mcp_url","action_mcp_auth","docs","verification_gates_usage","free_plan"],"title":"Onboarding","description":"How an agent provisions its own access — the discover→signup→verify→render loop.\n\nSurfaced here (unauthenticated) so a capabilities-first agent can go from finding\nFramelane to holding a working key without a human or reading prose docs."},"OnboardingDocs":{"properties":{"llms_txt":{"type":"string","title":"Llms Txt","description":"Concise agent index (llmstxt.org convention)."},"skill":{"type":"string","title":"Skill","description":"Installable, self-contained capability guide."},"raw_markdown":{"type":"string","title":"Raw Markdown","description":"How to fetch any docs page as raw markdown (append `.md`)."}},"type":"object","required":["llms_txt","skill","raw_markdown"],"title":"OnboardingDocs","description":"Static, account-free documentation channels for agents (no docs-search MCP)."},"OnboardingStep":{"properties":{"method":{"type":"string","title":"Method","description":"HTTP method, e.g. `POST`."},"path":{"type":"string","title":"Path","description":"Endpoint path, e.g. `/v1/signup`."},"auth_required":{"type":"boolean","title":"Auth Required","description":"Whether this step needs an API key."},"description":{"type":"string","title":"Description","description":"What this step does."}},"type":"object","required":["method","path","auth_required","description"],"title":"OnboardingStep"},"Operation":{"properties":{"id":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Id","description":"A stable id for editors."},"target":{"type":"string","minLength":1,"title":"Target","description":"The element id it drives; with a `child` split, a frame's.","example":"donut"},"start":{"type":"number","title":"Start","description":"Seconds after the group's anchor.","example":0.0},"end":{"type":"number","title":"End","description":"Seconds after the group's anchor; equal to `start` switches at once.","example":1.0},"property":{"type":"string","title":"Property","description":"A property registry path: `position.x`, `size.y`, `scale`, `rotation`, `opacity`, `fill`, `geometry.ring.segments[0].sweep`, `text.value`, …; a colour takes `.r`/`.g`/`.b`/`.a`. GET /v1/capabilities lists every path, its unit and range under `registry.properties`.","example":"geometry.ring.segments[0].sweep"},"from":{"anyOf":[{"type":"number"},{"type":"string"},{"type":"null"}],"title":"From","description":"Value at `start` (a string on `text.value`). Omitted on a replace: the value below it at `start`; on an add or multiply: the identity."},"to":{"anyOf":[{"type":"number"},{"type":"string"},{"type":"null"}],"title":"To","description":"Value at `end`, held after it. Omitted on a replace: the live base value; on an add or multiply: the identity."},"easing":{"anyOf":[{"oneOf":[{"$ref":"#/components/schemas/Easing"},{"$ref":"#/components/schemas/EaseFamily"},{"$ref":"#/components/schemas/EaseBezier"},{"$ref":"#/components/schemas/EaseSpring"},{"$ref":"#/components/schemas/EaseBack"},{"$ref":"#/components/schemas/EaseElastic"}]},{"type":"null"}],"title":"Easing","description":"A curve name (`ease_in_out`, `hold`, …), `{family, intensity}`, or one of `{bezier: [x1, y1, x2, y2]}`, `{spring}`, `{back}`, `{elastic}`. Omitted: linear."},"composite":{"anyOf":[{"type":"string","enum":["replace","add","multiply"]},{"type":"null"}],"title":"Composite","description":"`replace` overrides what is below it from `start`; `add` and `multiply` stack on it (multiply on scale, opacity and colours). Omitted: `replace`."},"split":{"anyOf":[{"$ref":"#/components/schemas/OperationSplit"},{"type":"null"}],"description":"Run it per child, letter, word or line, staggered."},"takeover":{"anyOf":[{"type":"string","const":"velocity"},{"type":"null"}],"title":"Takeover","description":"On a replace without `from`: start at the speed of what it replaces."}},"additionalProperties":false,"type":"object","required":["target","start","end","property"],"title":"Operation","description":"One property driven over a window (GVR §4.3): the base value, then the replaces by\nstart, then the multiplies and the adds. It holds `to` after `end`."},"OperationAnchor":{"properties":{"ref":{"type":"string","enum":["composition","element.start","element.end","marker","instance.start"],"title":"Ref","description":"`composition` (0 s), `element.start` or `element.end` of `target`, a marker by `name`, or `instance.start` of `target` (in a component, its own instance)."},"target":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Target","description":"The element or instance an element or instance anchor reads."},"name":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Name","description":"The marker a `marker` anchor reads."},"offset":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Offset","description":"Seconds added to the anchor. Omitted: 0."}},"additionalProperties":false,"type":"object","required":["ref"],"title":"OperationAnchor","description":"Where a group's times count from (GVR §4.3 item 8)."},"OperationGroup":{"properties":{"id":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Id","description":"A stable id for editors."},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"A label, e.g. `Animate in`."},"role":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Role","description":"What the group is for in editors (`in`, `out`, `loop`, …). Not sent to the renderer."},"anchor":{"anyOf":[{"$ref":"#/components/schemas/OperationAnchor"},{"type":"null"}],"description":"Where its times count from. Required in the request's `operations`; a component's groups default to `instance.start`."},"ops":{"items":{"oneOf":[{"$ref":"#/components/schemas/Operation"},{"$ref":"#/components/schemas/PresetOperation"}]},"type":"array","title":"Ops","description":"Operations and presets in document order: at equal starts the later replace wins."}},"additionalProperties":false,"type":"object","required":["ops"],"title":"OperationGroup","description":"Operations sharing one anchor (GVR §4.3)."},"OperationMarker":{"properties":{"name":{"type":"string","minLength":1,"title":"Name","example":"beat"},"time":{"type":"number","title":"Time","description":"Seconds on the composition clock.","example":1.5}},"additionalProperties":false,"type":"object","required":["name","time"],"title":"OperationMarker","description":"A named time a `marker` anchor reads."},"OperationSplit":{"properties":{"unit":{"type":"string","enum":["element","child","letter","word","line"],"title":"Unit","description":"`child` runs over a frame's children; `letter`, `word` and `line` over a text's."},"stagger":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Stagger","description":"Seconds between units. Omitted: 0."},"order":{"anyOf":[{"type":"string","enum":["forward","reverse","centre","edges","seeded"]},{"type":"null"}],"title":"Order","description":"The order the units take their turns in. Omitted: `forward`."}},"additionalProperties":false,"type":"object","required":["unit"],"title":"OperationSplit","description":"Run the operation once per unit, each starting `rank × stagger` seconds after the\noperation's start, the rank by `order` (GVR §4.3 item 7)."},"OperationsBlock":{"properties":{"markers":{"items":{"$ref":"#/components/schemas/OperationMarker"},"type":"array","title":"Markers","description":"Named times `marker` anchors read.","default":[]},"groups":{"items":{"$ref":"#/components/schemas/OperationGroup"},"type":"array","maxItems":1000,"title":"Groups","description":"At most 1000 groups and 10000 operations.","default":[]}},"additionalProperties":false,"type":"object","title":"OperationsBlock","description":"The `operations` block (render-node AEC §3; GVR §4.3): element properties driven over\ntime and evaluated by the engine every frame. Each group counts from its anchor."},"OutputFormat":{"type":"string","enum":["mp4","webm","mov","gif","png","jpg"],"title":"OutputFormat","description":"Values:\n- `mp4` — H.264 MP4 video — the default, widely compatible delivery format.\n- `webm` — VP9 WebM video — supports a transparent (alpha) background.\n- `mov` — QuickTime MOV — ProRes 4444 with a transparent (alpha) background; large files.\n- `gif` — Animated GIF — roadmap; not yet emitted by the renderer.\n- `png` — Single-frame PNG still — roadmap; not yet emitted by the renderer.\n- `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."}},"OutputFormatCap":{"properties":{"value":{"type":"string","title":"Value","description":"The `output_format` value."},"description":{"type":"string","title":"Description","description":"What this format produces."},"supported":{"type":"boolean","title":"Supported","description":"Whether the renderer can emit this format today."},"alpha_capable":{"type":"boolean","title":"Alpha Capable","description":"Whether it can carry a transparent (alpha) channel."}},"type":"object","required":["value","description","supported","alpha_capable"],"title":"OutputFormatCap"},"Palette":{"properties":{"primary":{"anyOf":[{"type":"string","pattern":"^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"},{"type":"null"}],"title":"Primary","description":"Dominant brand color."},"secondary":{"anyOf":[{"type":"string","pattern":"^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"},{"type":"null"}],"title":"Secondary","description":"Supporting color."},"accent":{"anyOf":[{"type":"string","pattern":"^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"},{"type":"null"}],"title":"Accent","description":"Highlight / CTA color."},"bg":{"anyOf":[{"type":"string","pattern":"^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"},{"type":"null"}],"title":"Bg","description":"Page background."},"ink":{"anyOf":[{"type":"string","pattern":"^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"},{"type":"null"}],"title":"Ink","description":"Body text color."}},"type":"object","title":"Palette","description":"Brand colors. Missing values mean 'not confidently extracted'."},"ParamSpec":{"properties":{"name":{"type":"string","title":"Name","description":"Parameter name in the task request body."},"type":{"type":"string","title":"Type","description":"JSON type: string, number, integer, boolean, array, or enum."},"required":{"type":"boolean","title":"Required","description":"Whether the parameter must be supplied."},"minimum":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Minimum","description":"Inclusive lower bound, if numeric."},"maximum":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Maximum","description":"Inclusive upper bound, if numeric."},"default":{"anyOf":[{},{"type":"null"}],"title":"Default","description":"Default value when omitted."},"options":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Options","description":"Allowed values, for enum params."},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"What the parameter controls."}},"type":"object","required":["name","type","required"],"title":"ParamSpec"},"PathKeyframe":{"properties":{"time":{"type":"number","minimum":0.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"},"Point":{"properties":{"x":{"type":"number","title":"X","default":0.5},"y":{"type":"number","title":"Y","default":0.5}},"additionalProperties":false,"type":"object","title":"Point"},"PresetOperation":{"properties":{"id":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Id","description":"A stable id for editors."},"target":{"type":"string","minLength":1,"title":"Target","description":"The element id it drives; with a `child` split, a frame's.","example":"donut"},"start":{"type":"number","title":"Start","description":"Seconds after the group's anchor.","example":0.0},"end":{"type":"number","title":"End","description":"Seconds after the group's anchor; equal to `start` switches at once.","example":1.0},"preset":{"type":"string","enum":["arcRise","bounceIn","drop","elasticRise","fade","floatDown","floatLeft","floatRight","floatUp","gentleFloatDown","gentleFloatLeft","gentleFloatRight","gentleFloatUp","identity","kenBurnsIn","kenBurnsInOut","kenBurnsOut","loop-3DSpin","loop-3DSway","loop-bounce","loop-heartBeat","loop-jiggle","loop-orbit","loop-rotateBasic","loop-rotateSmooth","loop-squeezy","loop-sway","out-bounceOut","out-dropOut","out-elasticDrop","out-fade","out-floatDown","out-floatLeft","out-floatRight","out-floatUp","out-gentleFloatDown","out-gentleFloatLeft","out-gentleFloatRight","out-gentleFloatUp","out-pop","out-slideBounceDown","out-slideBounceLeft","out-slideBounceRight","out-slideBounceUp","out-slideDown","out-slideLeft","out-slideRight","out-slideUp","out-spinAntiClockwise","out-spinClockwise","out-swingOut","out-wipeDown","out-wipeLeft","out-wipeRight","out-wipeUp","out-zoomOut","pop","slideBounceDown","slideBounceLeft","slideBounceRight","slideBounceUp","slideDown","slideLeft","slideRight","slideUp","smoothPop","spinAntiClockwise","spinClockwise","springPop","swingIn","tiltZoom","wipeDown","wipeLeft","wipeRight","wipeUp","zoomIn"],"title":"Preset","description":"An element preset by the engine's name (`slideUp`, `zoomIn`, `fade`, …). Text presets stay on a text's `motion`.","example":"zoomIn"}},"additionalProperties":false,"type":"object","required":["target","start","end","preset"],"title":"PresetOperation","description":"An element preset run as operations over the window (GVR §4.3 item 10)."},"PreviewOptions":{"properties":{"at":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"At","description":"Timeline seconds for a single-frame preview, snapped to the frame grid. Returns an unlabelled PNG, viewable inline via `get_render_preview`; a `window` preview is video and is not. Mutually exclusive with 'window' and 'contact_sheet'."},"window":{"anyOf":[{"$ref":"#/components/schemas/PreviewWindow"},{"type":"null"}],"description":"Time range for a short clip preview (with audio), or the range a contact_sheet spans."},"contact_sheet":{"anyOf":[{"$ref":"#/components/schemas/ContactSheetOptions"},{"type":"null"}],"description":"Produce a contact sheet (grid of composition-aware frames) instead of a single frame or clip."},"width":{"anyOf":[{"type":"integer","maximum":3840.0,"minimum":16.0},{"type":"null"}],"title":"Width","description":"Preview width in px; height derives from the aspect ratio. Defaults to a small draft width."},"dry_run":{"type":"boolean","title":"Dry Run","description":"Validate only: run the linter and return violations, no render.","default":false},"target_duration_sec":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Target Duration Sec","description":"Optional target timeline length; emits TARGET_DURATION_MISSED when outside tolerance."},"target_tolerance_sec":{"type":"number","minimum":0.0,"title":"Target Tolerance Sec","default":0.5}},"additionalProperties":false,"type":"object","title":"PreviewOptions","description":"The knobs shared by a standalone preview and a stored-project preview.\n\nEverything except *which* composition to preview: an ``at`` frame or a\n``window`` clip (mutually exclusive), the draft ``width``, ``dry_run`` for\nvalidate-only, and an optional target-duration check."},"PreviewRequest":{"properties":{"at":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"At","description":"Timeline seconds for a single-frame preview, snapped to the frame grid. Returns an unlabelled PNG, viewable inline via `get_render_preview`; a `window` preview is video and is not. Mutually exclusive with 'window' and 'contact_sheet'."},"window":{"anyOf":[{"$ref":"#/components/schemas/PreviewWindow"},{"type":"null"}],"description":"Time range for a short clip preview (with audio), or the range a contact_sheet spans."},"contact_sheet":{"anyOf":[{"$ref":"#/components/schemas/ContactSheetOptions"},{"type":"null"}],"description":"Produce a contact sheet (grid of composition-aware frames) instead of a single frame or clip."},"width":{"anyOf":[{"type":"integer","maximum":3840.0,"minimum":16.0},{"type":"null"}],"title":"Width","description":"Preview width in px; height derives from the aspect ratio. Defaults to a small draft width."},"dry_run":{"type":"boolean","title":"Dry Run","description":"Validate only: run the linter and return violations, no render.","default":false},"target_duration_sec":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Target Duration Sec","description":"Optional target timeline length; emits TARGET_DURATION_MISSED when outside tolerance."},"target_tolerance_sec":{"type":"number","minimum":0.0,"title":"Target Tolerance Sec","default":0.5},"render_request":{"$ref":"#/components/schemas/RenderRequest","description":"The composition to preview."}},"additionalProperties":false,"type":"object","required":["render_request"],"title":"PreviewRequest"},"PreviewResponse":{"properties":{"editor_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Editor Url","description":"Open the previewed project in the authenticated console. Absent for an inline preview."},"kind":{"type":"string","title":"Kind","description":"'validation' (dry-run), 'frame', 'window', or 'contact_sheet'."},"ok":{"type":"boolean","title":"Ok","description":"True when there are no error-severity violations."},"violations":{"items":{"$ref":"#/components/schemas/Violation"},"type":"array","title":"Violations"},"job":{"anyOf":[{"$ref":"#/components/schemas/JobOut"},{"type":"null"}],"description":"For a pixel preview: the preview render job to poll via GET /v1/renders/{id} (is_preview=true, not billed). Absent for a dry-run."},"sheet":{"anyOf":[{"$ref":"#/components/schemas/ContactSheetPlan"},{"type":"null"}],"description":"For a contact_sheet: the sampling plan (cells + grid shape). Present even on a dry-run."},"task":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Task","description":"For a dry-run (not a contact sheet): the render task this composition translates to, the JSON the render engine parses. It is the lenient translation a preview dispatches: a feature a real submit would refuse is left out and reported in `violations`. Source URLs are as submitted. Absent otherwise."}},"type":"object","required":["kind","ok"],"title":"PreviewResponse"},"PreviewWindow":{"properties":{"from":{"type":"number","minimum":0.0,"title":"From","description":"Window start in timeline seconds."},"to":{"type":"number","exclusiveMinimum":0.0,"title":"To","description":"Window end in timeline seconds."}},"additionalProperties":false,"type":"object","required":["from","to"],"title":"PreviewWindow","description":"A ``[from, to]`` time range, in timeline seconds."},"ProgressStage":{"type":"string","enum":["downloading","compositing","encoding","uploading"],"title":"ProgressStage","description":"Values:\n- `downloading` — Fetching source media.\n- `compositing` — Compositing the scene on the GPU.\n- `encoding` — Encoding the output video.\n- `uploading` — Uploading the finished artifact to storage.","x-enumDescriptions":{"downloading":"Fetching source media.","compositing":"Compositing the scene on the GPU.","encoding":"Encoding the output video.","uploading":"Uploading the finished artifact to storage."}},"ProjectCreate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Name","description":"Display name. Defaults to 'Untitled project', or for a copy to 'Copy of {source}' (same kind), '{source} Template' (a project saved as a template) or the template's name without ' Template' (a project made from a template)."},"render_request":{"anyOf":[{"$ref":"#/components/schemas/RenderRequest"},{"type":"null"}],"description":"Seed composition. An empty project is created when omitted."},"source_project_id":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Source Project Id","description":"Copy this project's head into version 1 of the new one: your own project or template, or a published template from GET /v1/templates. Element ids are kept. Not together with `render_request`."},"kind":{"type":"string","enum":["project","template"],"title":"Kind","description":"`template` creates a template (a project used as a copy source).","default":"project"},"author":{"$ref":"#/components/schemas/RevisionAuthor","description":"Who is creating this project, for version provenance — `user` or `agent`. Name the values here because MCP advertises this body as an opaque object: a caller sees the key but not its enum, so anything else is learned only from the 422.","default":"user"}},"additionalProperties":false,"type":"object","title":"ProjectCreate"},"ProjectCreated":{"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."},"editor_url":{"type":"string","title":"Editor Url","description":"Open this project in the authenticated console."},"kind":{"type":"string","enum":["project","template"],"title":"Kind","description":"`project`, or `template`: a project used as a copy source.","default":"project"},"source_project_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Project Id","description":"The project or template this one was copied from."},"source_kind":{"anyOf":[{"type":"string","enum":["project","template","public_template"]},{"type":"null"}],"title":"Source Kind","description":"What the source was: `project` (a duplicate), `template` (one of your templates) or `public_template` (a published template from the gallery)."},"thumbnail_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thumbnail Url","description":"A poster image of the head; null until one is generated."},"thumbnail_version":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Thumbnail Version","description":"The head version the thumbnail shows. It is stale while this is below `version`."},"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."},"ok":{"type":"boolean","title":"Ok","description":"Advisory — the project was still created and stored at version 1. False means the seed composition has error-severity findings; a project is allowed to hold an invalid head while you repair it with ops.","example":true},"violations":{"items":{"$ref":"#/components/schemas/Violation"},"type":"array","title":"Violations","description":"Advisory — the project was still created and stored at version 1. The same findings POST /v1/projects/{id}/ops returns after an edit, so you can see what needs repairing without a round trip.Truncated to the first 36 findings (errors first) on very large compositions, with a VIOLATIONS_TRUNCATED marker carrying the full count; the free dry run is never truncated."}},"type":"object","required":["id","name","version","workspace_id","editor_url","created_at","updated_at","summary","ok"],"title":"ProjectCreated","description":"A freshly created project, plus the advisory lint of its seed composition.\n\nReturned only by ``POST /v1/projects``. A *subclass* rather than two more fields\non :class:`ProjectOut`, because ``ProjectOut`` also backs ``GET /v1/projects``,\nwhich would then have to either carry permanently-null fields or lint every\nproject on the page."},"ProjectDiff":{"properties":{"added_ids":{"items":{"type":"string"},"type":"array","title":"Added Ids","description":"Element ids, at any depth, present only after the edit."},"removed_ids":{"items":{"type":"string"},"type":"array","title":"Removed Ids","description":"Element ids, at any depth, present only before the edit."},"moved_ids":{"items":{"type":"string"},"type":"array","title":"Moved Ids","description":"Element ids whose parent changed: a frame, an instance's slot or the root."},"transitions_changed":{"type":"boolean","title":"Transitions Changed","description":"True if the transition list changed.","default":false},"operations_changed":{"type":"boolean","title":"Operations Changed","description":"True if the `operations` block changed.","default":false},"components_changed":{"type":"boolean","title":"Components Changed","description":"True if a component definition changed.","default":false}},"type":"object","title":"ProjectDiff","description":"A structural delta between the previous head and the new head."},"ProjectElementSummary":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"Element id (the op target)."},"parent":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent","description":"The frame or instance (for its slot content) it sits in; null at the root."},"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."},"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."},"editor_url":{"type":"string","title":"Editor Url","description":"Open this project in the authenticated console."},"kind":{"type":"string","enum":["project","template"],"title":"Kind","description":"`project`, or `template`: a project used as a copy source.","default":"project"},"source_project_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Project Id","description":"The project or template this one was copied from."},"source_kind":{"anyOf":[{"type":"string","enum":["project","template","public_template"]},{"type":"null"}],"title":"Source Kind","description":"What the source was: `project` (a duplicate), `template` (one of your templates) or `public_template` (a published template from the gallery)."},"thumbnail_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thumbnail Url","description":"A poster image of the head; null until one is generated."},"thumbnail_version":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Thumbnail Version","description":"The head version the thumbnail shows. It is stale while this is below `version`."},"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","editor_url","created_at","updated_at","summary"],"title":"ProjectOut","description":"A project. ``render_request`` is populated only for ``view=full``."},"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","description":"Elements at every depth: the root's, each frame's children and each instance's slot content.","default":0},"transition_count":{"type":"integer","title":"Transition Count","default":0},"frame_count":{"type":"integer","title":"Frame Count","description":"Frames, at every depth.","default":0},"instance_count":{"type":"integer","title":"Instance Count","description":"Instances, at every depth.","default":0},"operation_count":{"type":"integer","title":"Operation Count","description":"Entries in the request's `operations` groups.","default":0},"component_count":{"type":"integer","title":"Component Count","description":"Component definitions.","default":0},"deepest":{"items":{"type":"string"},"type":"array","title":"Deepest","description":"Ids from a root element down to the most deeply nested element (the first in document order); empty when nothing nests."},"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."},"ProjectUpdate":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name","description":"New display name."}},"additionalProperties":false,"type":"object","required":["name"],"title":"ProjectUpdate"},"ProjectView":{"type":"string","enum":["summary","full"],"title":"ProjectView","description":"How much of a project to return from ``GET /v1/projects/{id}``."},"RangeSelector":{"properties":{"based_on":{"type":"string","enum":["glyph","word"],"title":"Based On","default":"glyph"},"start":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Start","description":"Range start, fraction along the text.","default":0.0},"end":{"type":"number","maximum":1.0,"minimum":0.0,"title":"End","description":"Range end, fraction along the text.","default":1.0},"falloff":{"type":"number","minimum":0.0,"title":"Falloff","description":"Ease to zero over this fraction beyond each edge.","default":0.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)."},"RateKeyframe":{"properties":{"time":{"type":"number","minimum":0.0,"title":"Time","description":"Timeline seconds."},"rate":{"type":"number","minimum":0.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."},"RateLimit":{"properties":{"scope":{"type":"string","title":"Scope","description":"Which action this limit applies to."},"limit":{"type":"integer","title":"Limit","description":"Maximum requests allowed per window."},"window_seconds":{"type":"integer","title":"Window Seconds","description":"Length of the rate-limit window in seconds."}},"type":"object","required":["scope","limit","window_seconds"],"title":"RateLimit"},"ReadinessComponent":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"}},"type":"object","required":["name","ok"],"title":"ReadinessComponent"},"ReadinessResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"components":{"items":{"$ref":"#/components/schemas/ReadinessComponent"},"type":"array","title":"Components"}},"type":"object","required":["ok","components"],"title":"ReadinessResponse"},"RemoveElementOp":{"properties":{"op":{"type":"string","const":"remove_element","title":"Op"},"id":{"type":"string","title":"Id"}},"additionalProperties":false,"type":"object","required":["op","id"],"title":"RemoveElementOp"},"RemoveTransitionOp":{"properties":{"op":{"type":"string","const":"remove_transition","title":"Op"},"from_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From Id"},"to_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"To Id"},"index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Index","description":"Remove the transition at this index (alternative to from_id/to_id)."}},"additionalProperties":false,"type":"object","required":["op"],"title":"RemoveTransitionOp"},"RenderRequest":{"properties":{"version":{"anyOf":[{"type":"string","enum":["v4","v5"]},{"type":"null"}],"title":"Version","description":"Wire version of this request. Omit it: the render task is v5 exactly when the request carries a v5 block, and v4 otherwise. `v5` accepts the v5 blocks (a shape's `geometry`, `frame` and `instance` elements, `operations`, `components`); `v4` refuses them.","example":"v5"},"schema_version":{"anyOf":[{"type":"string","const":"v5"},{"type":"null"}],"title":"Schema Version","description":"The document format an editor wrote this request in. Not sent to the renderer; `version` is the wire's.","example":"v5"},"width":{"anyOf":[{"type":"integer","maximum":8192.0,"minimum":16.0},{"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.0,"minimum":16.0},{"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.0},{"type":"null"}],"title":"Duration","description":"Total composition duration in seconds. Inferred from elements when omitted.","example":15.0},"frame_rate":{"anyOf":[{"type":"integer","maximum":240.0,"minimum":1.0},{"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"},{"$ref":"#/components/schemas/FrameElement"},{"$ref":"#/components/schemas/InstanceElement"}],"discriminator":{"propertyName":"type","mapping":{"audio":"#/components/schemas/AudioElement","frame":"#/components/schemas/FrameElement","image":"#/components/schemas/ImageElement","instance":"#/components/schemas/InstanceElement","shape":"#/components/schemas/ShapeElement","text":"#/components/schemas/TextElement","video":"#/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), the alias of frames (§5). A request with `frame` or `instance` elements carries none."},"design_size":{"anyOf":[{"$ref":"#/components/schemas/DesignSize"},{"type":"null"}],"description":"The logical root in design px: frames and instances at the root are placed in this box, and the render scales it uniformly to `width` x `height`, whose aspect it must have. Canvas percent at the root is unchanged."},"operations":{"anyOf":[{"$ref":"#/components/schemas/OperationsBlock"},{"type":"null"}],"description":"Element properties driven over time (GVR §4.3): groups of operations, each counting from an anchor. Schema: `blocks.operations` in GET /v1/capabilities."},"components":{"items":{"$ref":"#/components/schemas/Component"},"type":"array","title":"Components","description":"Component definitions, which `instance` elements place. Schema of one: `blocks.components` in GET /v1/capabilities."},"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 video/audio source URLs that are not hosted by Framelane. When `true` (the default), each such file is probed for its duration before rendering: the job starts in the `ingesting` state and moves to `queued` once the probe answers, so elements may omit `out_point`. The file itself is fetched by the renderer at render time, so the URL must be a direct, public https link to the media (not a share page) that supports range requests. When `false`, the URL is passed straight through and video/audio require an explicit `out_point`.","example":true}},"additionalProperties":false,"type":"object","title":"RenderRequest","description":"Body for ``POST /v1/renders``.\n\nDefines the composition to render: dimensions, output format, timeline elements,\nand optional transitions. The render engine produces a single video artifact."},"RenderSubmitOut":{"properties":{"id":{"type":"string","title":"Id","description":"Unique job ID. Prefix indicates type: `render_` or `task_`.","example":"render_01J8QR2K5VKDGN2T4FBM3CZYX7"},"kind":{"$ref":"#/components/schemas/JobKind","description":"Whether this is a `render` or a `task`.","example":"render"},"is_preview":{"type":"boolean","title":"Is Preview","description":"True for a cheap preview render. Previews are not billed and are excluded from the renders list.","default":false,"example":false},"editor_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Editor Url","description":"Open the project this job was submitted from in the authenticated console. Absent for a job with no project."},"task_type":{"anyOf":[{"$ref":"#/components/schemas/TaskType"},{"type":"null"}],"description":"For `task` jobs only — the specific AI operation.","example":"transcribe"},"workspace_id":{"type":"string","title":"Workspace Id","description":"ID of the workspace that owns this job.","example":"ws_01J8QR2K5VKDGN2T4FBM3CZYX8"},"status":{"$ref":"#/components/schemas/JobStatus","description":"Current lifecycle state: `queued` → `processing` → `completed` / `failed` / `cancelled`.","example":"queued"},"progress_percent":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Progress Percent","description":"0–100 progress indicator updated by the render engine.","default":0,"example":0},"progress_stage":{"anyOf":[{"$ref":"#/components/schemas/ProgressStage"},{"type":"null"}],"description":"Current processing phase within a job.","example":"compositing"},"output":{"anyOf":[{"$ref":"#/components/schemas/JobOutput"},{"type":"null"}],"description":"Populated once `status == completed`. Contains the signed artifact URL."},"result":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Result","description":"Structured JSON result for tasks that return data rather than a file — `transcribe` returns the transcript here (`text`, `words`, `utterances`, and any requested analysis). Populated once `status == completed`; `null` for renders and for any job whose only artifact is the file at `output.url`.","example":{"language_code":"en","text":"Welcome back to the show."}},"error":{"anyOf":[{"$ref":"#/components/schemas/JobError"},{"type":"null"}],"description":"Populated when `status == failed`. Contains a structured error code."},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Caller-supplied key-value pairs echoed back on every webhook and response.","example":{"project_id":"proj_123","user_ref":"usr_abc"}},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"ISO-8601 UTC timestamp when the job was created."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"ISO-8601 UTC timestamp of the last status change."},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At","description":"ISO-8601 UTC timestamp when the job reached a terminal state."},"ok":{"type":"boolean","title":"Ok","description":"Advisory — the render was still accepted and dispatched. False means the composition has error-severity findings that will likely make the output wrong (a self-transition, a fully off-canvas element); it does NOT mean the submission was refused. Everything that actually blocks a render is a 422 before you ever see this field.","example":true},"violations":{"items":{"$ref":"#/components/schemas/Violation"},"type":"array","title":"Violations","description":"Advisory — the render was still accepted and dispatched. Cross-element and layout findings from the same linter the preview dry-run runs, attached so you can see problems on the render you just paid for instead of only in the artifact. To catch these BEFORE spending a render, call POST /v1/preview with dry_run.Truncated to the first 36 findings (errors first) on very large compositions, with a VIOLATIONS_TRUNCATED marker carrying the full count; the free dry run is never truncated."}},"type":"object","required":["id","kind","workspace_id","status","created_at","updated_at","ok"],"title":"RenderSubmitOut","description":"A freshly submitted render: the job row plus the advisory lint of its composition.\n\nReturned only by the two submit routes (``POST /v1/renders``,\n``POST /v1/projects/{id}/renders``). A *subclass* rather than two more fields on\n:class:`JobOut`, because ``JobOut`` is the shape of a job row on every read route\n(``GET /v1/renders``, all ``/v1/tasks`` routes, the MCP read tools) where a lint\nresult does not exist and would be permanently null."},"ReorderOp":{"properties":{"op":{"type":"string","const":"reorder","title":"Op"},"id":{"type":"string","title":"Id"},"to_index":{"type":"integer","minimum":0.0,"title":"To Index","description":"New position among its siblings."}},"additionalProperties":false,"type":"object","required":["op","id","to_index"],"title":"ReorderOp"},"ReplaceRequestOp":{"properties":{"op":{"type":"string","const":"replace_request","title":"Op"},"render_request":{"$ref":"#/components/schemas/RenderRequest","description":"Escape hatch: replace the whole composition."}},"additionalProperties":false,"type":"object","required":["op","render_request"],"title":"ReplaceRequestOp"},"RevisionAuthor":{"type":"string","enum":["user","agent"],"title":"RevisionAuthor","description":"Who authored a version -- the human owner or an agent acting on their behalf."},"RotationXYZ":{"properties":{"x":{"type":"number","title":"X","default":0.0},"y":{"type":"number","title":"Y","default":0.0},"z":{"type":"number","title":"Z","default":0.0}},"additionalProperties":false,"type":"object","title":"RotationXYZ","description":"3D rotation (degrees) for a custom-animation keyframe.\n\nNon-zero `x`/`y` renders true perspective tilt through the engine's 45° fov\ncamera — the animated 3D-card family (spins, tilting reveals). This is also\nthe engine-recommended way to tilt *text*, which has no static tilt."},"SampleReason":{"type":"string","enum":["first_frame","element_start","transition","caption","uniform","last_frame"],"title":"SampleReason","description":"Why a contact-sheet cell was sampled -- what the frame is showing.\n\nComposition-aware: because we own the timeline, cells land on meaningful\nmoments (an element entering, a transition, a caption starting) instead of\nblind scene detection, and each carries its reason so the agent knows what\nit is looking at."},"ScaleXY":{"properties":{"x":{"type":"number","title":"X","default":1.0},"y":{"type":"number","title":"Y","default":1.0}},"additionalProperties":false,"type":"object","title":"ScaleXY","description":"Non-uniform scale for a custom-animation keyframe."},"SetFieldsOp":{"properties":{"op":{"type":"string","const":"set_fields","title":"Op"},"id":{"type":"string","title":"Id"},"fields":{"additionalProperties":true,"type":"object","title":"Fields","description":"Element fields to patch; re-validated against the element type. Patching anything that moves the element's window (`time`, `duration`, `in_point`/`out_point`, `speed`) carries its animations and keyframes with it; a field you pass explicitly is used verbatim. On an instance, `properties` and `slots` merge by key, and a key set to null is removed (the property takes its default, the slot its own content)."},"component":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Component","description":"Edit the element `id` in this component's definition instead (its body, and every variant body that holds it), which every instance shows. Omitted: the composition's own element."}},"additionalProperties":false,"type":"object","required":["op","id","fields"],"title":"SetFieldsOp"},"SetRequestFieldsOp":{"properties":{"op":{"type":"string","const":"set_request_fields","title":"Op"},"fields":{"additionalProperties":true,"type":"object","title":"Fields","description":"Top-level RenderRequest fields to patch in place (groups, custom_animations, motion_blur, background_gradient/background_color, width/height/duration/frame_rate, output_format, watermark_url, alpha, metadata, …), re-validated against RenderRequest. `elements`/`transitions` are rejected — the element and transition ops own those. Patching only top-level fields touches no element, so `changed_ids` comes back empty; the version still bumps."}},"additionalProperties":false,"type":"object","required":["op","fields"],"title":"SetRequestFieldsOp"},"SetTransitionOp":{"properties":{"op":{"type":"string","const":"set_transition","title":"Op"},"transition":{"$ref":"#/components/schemas/Transition","description":"Add or replace the transition between from_id and to_id."}},"additionalProperties":false,"type":"object","required":["op","transition"],"title":"SetTransitionOp"},"Severity":{"type":"string","enum":["error","warning"],"title":"Severity"},"ShaderCaps":{"properties":{"compiler_available":{"type":"boolean","title":"Compiler Available","description":"Whether `POST /v1/shaders` can compile on this deployment; while false it answers 503 `shader_compiler_unavailable`."},"renders":{"type":"boolean","title":"Renders","description":"Whether this deployment sends `effects[].shader` to the engine (a v5 task); while false such an effect is refused with `unsupported_feature`."},"entry":{"additionalProperties":true,"type":"object","title":"Entry","description":"JSON Schema of `effects[].shader`."},"element_types":{"items":{"type":"string"},"type":"array","title":"Element Types","description":"Element types whose `effects` take a `shader`."},"param_types":{"additionalProperties":{"type":"string"},"type":"object","title":"Param Types","description":"Each param type and how a value of it is spelled."},"limits":{"additionalProperties":{"type":"number"},"type":"object","title":"Limits","description":"The engines' limits (RuntimeShaderLimits.h), which the compiler enforces too."},"flow":{"items":{"type":"string"},"type":"array","title":"Flow","description":"The authoring loop, in order."},"prelude":{"additionalProperties":{"additionalProperties":{"type":"string"},"type":"object"},"type":"object","title":"Prelude","description":"What a source may use beside its params, each with its meaning: `uniforms`, `functions`, and `jitter`, the aliases of Jitter's dialect (a `jitterMain` entry)."}},"type":"object","required":["compiler_available","renders","entry","element_types","param_types","limits","flow","prelude"],"title":"ShaderCaps","description":"Runtime shaders (render-node AEC §2, GVR SH7): SkSL source in, the engines' compiled\nvariants out, run by any effect chain that names the shader's id."},"ShaderCompileError":{"properties":{"line":{"type":"integer","title":"Line","description":"1-based line in the source; 0 when the error is not in it (a param)."},"col":{"type":"integer","title":"Col","description":"1-based column in the source; 0 with line 0."},"message":{"type":"string","title":"Message"}},"type":"object","required":["line","col","message"],"title":"ShaderCompileError"},"ShaderCreate":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name","description":"A label, carried into renders as the entry's name."},"lang":{"type":"string","const":"sksl","title":"Lang","description":"The source language; SkSL is the only one.","default":"sksl"},"source":{"type":"string","minLength":1,"title":"Source","description":"The SkSL body: `half4 main(float2 uv)` (uv 0 to 1 from the top-left), or Jitter's `half4 jitterMain(float2 uv)`. No `uniform` declarations: uniforms come from the prelude and `params`."},"jitter":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Jitter","description":"Compile in Jitter's dialect. Omitted: when the source defines `jitterMain`."},"params":{"items":{"$ref":"#/components/schemas/ShaderParamSpec"},"type":"array","title":"Params","description":"The params the source reads, in order.","default":[]}},"additionalProperties":false,"type":"object","required":["name","source"],"title":"ShaderCreate","description":"``POST /v1/shaders``: compile an SkSL source and store it."},"ShaderOut":{"properties":{"id":{"type":"string","title":"Id","description":"Its id, for `effects[].shader.id`.","example":"sh_01J8QR2K5VKDGN2T4FBM3CZYX7"},"name":{"type":"string","title":"Name"},"lang":{"type":"string","title":"Lang"},"hash":{"type":"string","title":"Hash","description":"SHA-256 of \"sksl\\n\" + source, 64 lowercase hex digits: the entry's identity."},"jitter":{"type":"boolean","title":"Jitter","description":"Compiled in Jitter's dialect."},"status":{"type":"string","enum":["compiled","failed"],"title":"Status","description":"`failed` shaders cannot render; `errors` says why."},"errors":{"items":{"$ref":"#/components/schemas/ShaderCompileError"},"type":"array","title":"Errors","description":"The compiler's errors, with positions.","default":[]},"params":{"items":{"$ref":"#/components/schemas/ShaderParamSpec"},"type":"array","title":"Params"},"variant_hashes":{"additionalProperties":{"type":"string"},"type":"object","title":"Variant Hashes","description":"SHA-256 of each variant (`glsl330`, `glsles300`, `wgsl`), as the engines check them.","default":{}},"shared":{"type":"boolean","title":"Shared","description":"A shared shader every workspace can use, and none can delete."},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source","description":"The SkSL source (`?view=full`)."},"variants":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Variants","description":"The compiled variants' text (`?view=full`)."},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","name","lang","hash","jitter","status","params","shared","created_at","updated_at"],"title":"ShaderOut","description":"A stored shader. `variants` and `source` come back with `?view=full` only."},"ShaderParamKeyframe":{"properties":{"time":{"type":"number","minimum":0.0,"title":"Time","description":"Absolute timeline seconds."},"value":{"anyOf":[{"type":"number"},{"type":"string"},{"items":{"type":"number"},"type":"array","maxItems":2,"minItems":2}],"title":"Value","description":"The param's value at this time, of its type."}},"additionalProperties":false,"type":"object","required":["time","value"],"title":"ShaderParamKeyframe","description":"A point on a runtime shader param's track (AEC §2): linear between, held outside."},"ShaderParamSpec":{"properties":{"name":{"type":"string","maxLength":64,"minLength":1,"title":"Name","description":"A letter, then letters, digits and single underscores; not a prelude name (`time` is taken by `u_time`). The shader reads it as `u_<name>`.","example":"speed"},"type":{"type":"string","enum":["float","color","vec2"],"title":"Type","description":"`float`, `color` (straight-alpha RGBA) or `vec2`."},"default":{"anyOf":[{"type":"number"},{"type":"string"},{"items":{"type":"number"},"type":"array","maxItems":2,"minItems":2},{"type":"null"}],"title":"Default","description":"Its value where an effect does not set it. Omitted: 0, transparent black or [0, 0]."},"min":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Min","description":"Lowest value an effect may set (a vec2's each component)."},"max":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Max","description":"Highest value an effect may set (a vec2's each component)."}},"additionalProperties":false,"type":"object","required":["name","type"],"title":"ShaderParamSpec","description":"A declared param: bound in the shader as `u_<name>`, set per effect by name."},"ShaderProbe":{"properties":{"width":{"type":"integer","maximum":8192.0,"minimum":16.0,"title":"Width","description":"Canvas width in px.","default":1280},"height":{"type":"integer","maximum":8192.0,"minimum":16.0,"title":"Height","description":"Canvas height in px.","default":720},"time":{"type":"number","minimum":0.0,"title":"Time","description":"The timeline second to render.","default":0.0},"params":{"additionalProperties":{"anyOf":[{"type":"number"},{"type":"string"},{"items":{"type":"number"},"type":"array","maxItems":2,"minItems":2},{"items":{"$ref":"#/components/schemas/ShaderParamKeyframe"},"type":"array","minItems":1}]},"type":"object","title":"Params","description":"Param values for the frame, as an effect's `shader.params` takes them.","default":{}}},"additionalProperties":false,"type":"object","title":"ShaderProbe","description":"``POST /v1/shaders/{id}/probe``: one frame of the shader on a full-canvas shape."},"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","description":"Glass: the scene behind the element, Gaussian-blurred, fills its footprint under its translucent content (a shape's own silhouette, a video's or image's box with its corner radius and mask). A Gaussian sigma in canvas px, as CSS `backdrop-filter: blur()` takes it; the renderer caps it at 128. 0 is off.","default":0},"backdrop_blur_keyframes":{"anyOf":[{"items":{"$ref":"#/components/schemas/BlurKeyframe"},"type":"array","minItems":1},{"type":"null"}],"title":"Backdrop Blur Keyframes","description":"Animate `backdrop_blur` over the timeline; replaces the static value while present."},"layer_blur":{"type":"number","maximum":128.0,"minimum":0.0,"title":"Layer Blur","description":"Blurs the element itself as drawn: a Gaussian sigma in canvas px, as CSS `filter: blur()` takes it, spreading three sigma past its box. At most 128; 0 is off. Not on a shape yet (the editor's preview cannot draw it), and not with `blend_mode`, `backdrop_blur` or `matte`, beside which the renderer would draw the element without it.","default":0},"layer_blur_keyframes":{"anyOf":[{"items":{"$ref":"#/components/schemas/BlurKeyframe"},"type":"array","minItems":1},{"type":"null"}],"title":"Layer Blur Keyframes","description":"Animate `layer_blur` over the timeline; replaces the static value while present."},"glass":{"anyOf":[{"$ref":"#/components/schemas/Glass"},{"type":"null"}],"description":"A refracting glass edge: the scene behind bends inward near the element's outline. Pair it with `backdrop_blur` for frosted glass."},"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.0,"minimum":0.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.0,"minimum":0.0},{"type":"null"}],"title":"Track","description":"Timeline track index (0–255). Informational only; not used by the renderer."},"time":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Time","description":"When this element starts on the output timeline, in seconds. Defaults to `0` when omitted.","example":0.0},"visible":{"type":"boolean","title":"Visible","description":"Set to `false` to skip this element without removing it from the request.","default":true},"locked":{"type":"boolean","title":"Locked","description":"Marks the element locked in editors. Not sent to the renderer.","default":false},"duration":{"type":"number","exclusiveMinimum":0.0,"title":"Duration","description":"**Required.** Seconds the shape is visible.","example":4.0},"path":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Path","description":"SVG path `d` (M L H V C S Q T A Z; elliptical arcs render as arcs). **Required** unless `geometry` is set.","example":"M50 0 L100 100 L0 100 Z"},"geometry":{"anyOf":[{"$ref":"#/components/schemas/ShapeGeometry"},{"type":"null"}],"description":"Draw a closed-form shape instead of a `path`: exactly one of `rect` (`corner_radius` per corner, `smoothing`), `ellipse`, `line`, `ring` or `arc` (`inner_ratio`, `start_angle`, `sweep`, `cap`, `segments`), `polygon` (`sides`, `corner_radius`) or `star` (`points`, `inner_ratio`, `corner_radius`), plus `stroke_align` and `shadows`. Lengths are px, angles degrees clockwise from 3 o'clock. The box, `fill`, `stroke`, `stroke_width`, `stroke_dash`, motion, masks, mattes, blend modes and effects work as on any shape; `path`, `path_keyframes`, `trim`, `corner_radius` and `shadow` do not apply."},"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,0.0,100.0,100.0]},"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","maximum":100000.0,"minimum":0.0},{"type":"null"}],"title":"Stroke Width","description":"Stroke width in viewBox units, at most 100000 (absent = SVG 1.0; 0 disables)."},"stroke_linecap":{"type":"string","enum":["butt","round","square"],"title":"Stroke Linecap","description":"Cap on the open ends of a stroked path (SVG `stroke-linecap`): `butt` stops at the endpoint, `round` and `square` extend it by half the stroke width. Closed paths have joins, not caps.","default":"round"},"stroke_linejoin":{"type":"string","enum":["miter","round","bevel"],"title":"Stroke Linejoin","description":"Corner join of a stroked path (SVG `stroke-linejoin`): `miter` is the sharp corner (subject to `stroke_miter_limit`), `bevel` cuts it flat.","default":"round"},"stroke_miter_limit":{"type":"number","minimum":1.0,"title":"Stroke Miter Limit","description":"SVG `stroke-miterlimit`: miter length over stroke width. A `miter` join sharper than this falls back to `bevel`.","default":4.0},"stroke_dash":{"anyOf":[{"items":{"type":"number"},"type":"array","minItems":1},{"type":"null"}],"title":"Stroke Dash","description":"Dash pattern in viewBox units, SVG `stroke-dasharray` semantics: `[on, off, ...]` of any length — an odd-length array repeats so on/off alternate (`[4]` is 4 on, 4 off). Trim applies before dash, so a dashed path can draw itself on without the pattern re-flowing."},"stroke_dash_offset":{"type":"number","title":"Stroke Dash Offset","description":"Where the dash pattern starts along the path, in viewBox units (SVG `stroke-dashoffset`): positive shifts the pattern forward, negative backward.","default":0.0},"stroke_dash_offset_keyframes":{"anyOf":[{"items":{"$ref":"#/components/schemas/DashOffsetKeyframe"},"type":"array"},{"type":"null"}],"title":"Stroke Dash Offset Keyframes","description":"Animate the dash offset (timeline seconds) — marching ants: keyframe `offset` linearly by one dash period (the pattern's sum) per second. Needs a `stroke_dash`; while present it drives the offset in place of the static `stroke_dash_offset`."},"stroke_marker_start":{"type":"string","enum":["none","triangle","open_chevron","dot"],"title":"Stroke Marker Start","description":"A head at the start of each open subpath, in the stroke's colour (render-node V2.6): `triangle` is filled, its tip on the endpoint (the shaft shortens to meet it); `open_chevron` is an open head stroked like the path; `dot` is a disc. Placed after the trim, so a draw-on keeps its head at the visible end, and left out of the dash. A closed subpath takes none. Needs a `stroke`; path shapes only.","default":"none"},"stroke_marker_end":{"type":"string","enum":["none","triangle","open_chevron","dot"],"title":"Stroke Marker End","description":"A head at the end of each open subpath; see `stroke_marker_start`.","default":"none"},"stroke_marker_size":{"type":"number","maximum":32.0,"minimum":1.0,"title":"Stroke Marker Size","description":"Marker length in stroke widths, 1–32 (4 is four times `stroke_width`).","default":4.0},"corner_radius":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.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"],"title":"ShapeElement","description":"A first-class vector primitive: SVG path fill + stroke, morphing (§5b)."},"ShapeGeometry":{"properties":{"rect":{"anyOf":[{"$ref":"#/components/schemas/GeometryRect"},{"type":"null"}]},"ellipse":{"anyOf":[{"$ref":"#/components/schemas/GeometryEllipse"},{"type":"null"}]},"line":{"anyOf":[{"$ref":"#/components/schemas/GeometryLine"},{"type":"null"}]},"ring":{"anyOf":[{"$ref":"#/components/schemas/GeometryRing"},{"type":"null"}]},"arc":{"anyOf":[{"$ref":"#/components/schemas/GeometryRing"},{"type":"null"}],"description":"The same parameters as `ring`."},"polygon":{"anyOf":[{"$ref":"#/components/schemas/GeometryPolygon"},{"type":"null"}]},"star":{"anyOf":[{"$ref":"#/components/schemas/GeometryStar"},{"type":"null"}]},"stroke_align":{"anyOf":[{"type":"string","enum":["inside","center","outside"]},{"type":"null"}],"title":"Stroke Align","description":"Where the stroke sits on the outline (`centre` is read as `center`). Omitted: `center`. A polygon, a star and a smoothed rect stroke centred."},"shadows":{"anyOf":[{"items":{"$ref":"#/components/schemas/GeometryShadow"},"type":"array","maxItems":4},{"type":"null"}],"title":"Shadows","description":"Up to 4 shadows, the first on top, on a rect or an ellipse drawn as a circle."}},"additionalProperties":false,"type":"object","title":"ShapeGeometry","description":"A closed-form shape the engines draw analytically (AEC §5f): exactly one kind."},"ShiftElementOp":{"properties":{"op":{"type":"string","const":"shift_element","title":"Op"},"id":{"type":"string","title":"Id","description":"Target element id."},"by_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"By Seconds","description":"Shift the element's start time by this many seconds (may be negative). The element's animations and keyframes travel with it, rigidly. A backward shift is clamped so nothing lands before 0 — the whole bundle stops together, keeping its internal spacing intact, which means the element itself may end up later than 0."},"by_frames":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"By Frames","description":"Shift by this many frames (converted via frame_rate). Use instead of by_seconds."}},"additionalProperties":false,"type":"object","required":["op","id"],"title":"ShiftElementOp"},"SignupRequest":{"properties":{"workspace_name":{"type":"string","maxLength":255,"minLength":1,"title":"Workspace Name","description":"Display name for the new workspace.","example":"Acme Corp"},"email":{"type":"string","maxLength":320,"format":"email","title":"Email","description":"Email address for the workspace owner. An OTP will be sent here.","example":"dev@acmecorp.com"},"api_key_name":{"type":"string","maxLength":255,"minLength":1,"title":"Api Key Name","description":"Label for the first API key.","default":"default","example":"default"}},"additionalProperties":false,"type":"object","required":["workspace_name","email"],"title":"SignupRequest"},"SignupResponse":{"properties":{"workspace":{"$ref":"#/components/schemas/WorkspaceOut"},"api_key":{"$ref":"#/components/schemas/ApiKeyCreated"},"otp_delivered":{"type":"boolean","title":"Otp Delivered","description":"Whether the verification email was actually dispatched. When false, the returned key is inert and cannot be verified — email delivery failed (or is not configured), so retry signup once a working inbox is reachable.","default":true}},"type":"object","required":["workspace","api_key"],"title":"SignupResponse"},"SignupVerifyRequest":{"properties":{"email":{"type":"string","maxLength":320,"format":"email","title":"Email","description":"The email address used during signup.","example":"dev@acmecorp.com"},"otp_code":{"type":"string","maxLength":6,"minLength":6,"pattern":"^\\d{6}$","title":"Otp Code","description":"The 6-digit OTP sent to the email.","example":"123456"}},"additionalProperties":false,"type":"object","required":["email","otp_code"],"title":"SignupVerifyRequest"},"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.\n\nThe segment from the previous keyframe's position travels along the bezier\ndefined by `c1`/`c2` (same units as `translate`) instead of a straight line."},"SpringEasing":{"properties":{"stiffness":{"type":"number","exclusiveMinimum":0.0,"title":"Stiffness","description":"Stiffness; higher lowers zeta, so the spring rings more.","default":100.0},"damping":{"type":"number","exclusiveMinimum":0.0,"title":"Damping","description":"Damping; higher raises zeta (less overshoot), lower rings longer.","default":10.0},"mass":{"type":"number","exclusiveMinimum":0.0,"title":"Mass","description":"Mass; like stiffness, higher lowers zeta, so the spring rings more. It does not swing slower: the segment's duration sets the timing.","default":1.0}},"additionalProperties":false,"type":"object","title":"SpringEasing","description":"Damped-spring ease over the keyframe segment.\n\nOnly the damping ratio zeta = damping / (2 * sqrt(stiffness * mass)) shapes the\ncurve; the segment's duration sets its timing. Below 1 the spring overshoots and\nrings (the defaults give zeta = 0.5); at 1 or above it settles without overshoot.\nAn underdamped spring runs until its envelope decays to 0.1% (settle x w0 =\nln(1000) / zeta, w0 = sqrt(stiffness / mass)). Since render-node's overdamped settle\nfix (GVR M3), a spring at zeta >= 1 runs over its slowest decay rate instead; before\nit, an overdamped spring's window was cut short and it reached the target still\nmoving."},"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): the groups alias's stack, placed as\nbefore. A `frame` has no layout of its own (the host's, render-node O27)."},"SwapSourceOp":{"properties":{"op":{"type":"string","const":"swap_source","title":"Op"},"id":{"type":"string","title":"Id"},"source_url":{"type":"string","title":"Source Url","description":"New source URL for a video/audio/image element."}},"additionalProperties":false,"type":"object","required":["op","id","source_url"],"title":"SwapSourceOp"},"TaskType":{"type":"string","enum":["transcribe"],"title":"TaskType","description":"The AI task types the API offers.\n\nValues:\n- `transcribe` — Speech-to-text transcription (via AssemblyAI) with optional analysis. Returns the transcript inline on the task's `result` object — there is no file artifact, so `output` is null and `/v1/tasks/{id}/download` returns 404.","x-enumDescriptions":{"transcribe":"Speech-to-text transcription (via AssemblyAI) with optional analysis. Returns the transcript inline on the task's `result` object — there is no file artifact, so `output` is null and `/v1/tasks/{id}/download` returns 404."}},"TaskTypeCap":{"properties":{"value":{"type":"string","title":"Value","description":"The task type — call `POST /v1/tasks/{value with - for _}`."},"description":{"type":"string","title":"Description","description":"What this task does."},"provider":{"type":"string","title":"Provider","description":"Backend that runs it: `renderer` or `assemblyai`."},"params":{"items":{"$ref":"#/components/schemas/ParamSpec"},"type":"array","title":"Params","description":"Tunable parameters and their constraints."}},"type":"object","required":["value","description","provider","params"],"title":"TaskTypeCap"},"TemplateDetail":{"properties":{"id":{"type":"string","title":"Id","description":"The template's project id."},"scope":{"type":"string","enum":["public","workspace"],"title":"Scope","description":"`public` (in the gallery, from a listing) or `workspace` (one of your templates)."},"name":{"type":"string","title":"Name","description":"The listing title for a public template, else the project name."},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Listing copy. Null for workspace templates."},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category","description":"Listing category. Null for workspace templates."},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"},"width":{"type":"integer","title":"Width","description":"Canvas width in pixels."},"height":{"type":"integer","title":"Height","description":"Canvas height in pixels."},"aspect":{"type":"string","enum":["landscape","portrait","square"],"title":"Aspect","description":"From width and height."},"duration":{"type":"number","title":"Duration","description":"Composition length in seconds."},"element_count":{"type":"integer","title":"Element Count"},"thumbnail_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thumbnail Url","description":"Poster image; null until one is generated."},"version":{"type":"integer","title":"Version","description":"The head version the card describes."},"thumbnail_version":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Thumbnail Version","description":"The head version the thumbnail shows; stale while below `version`."},"preview_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Preview Url","description":"A finished render to play on hover. Public templates only for now."},"premium":{"type":"boolean","title":"Premium","default":false},"listing_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Listing Id","description":"The gallery listing, for public templates."},"slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slug","description":"The listing's stable handle: GET /v1/templates/{slug}."},"priority":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Priority","description":"Listing order, highest first."},"status":{"anyOf":[{"type":"string","enum":["draft","published"]},{"type":"null"}],"title":"Status","description":"Listing status. `draft` reaches admin workspaces only."},"family":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Family","description":"Links the size variants of one design."},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"render_request":{"$ref":"#/components/schemas/RenderRequest","description":"The template's head composition."}},"type":"object","required":["id","scope","name","width","height","aspect","duration","element_count","version","created_at","updated_at","render_request"],"title":"TemplateDetail","description":"A gallery card plus the composition, so it can be inspected before it is used."},"TemplateListingCreate":{"properties":{"project_id":{"type":"string","title":"Project Id","description":"A kind=template project in your workspace."},"slug":{"type":"string","maxLength":80,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","title":"Slug","description":"Stable public handle, e.g. `stock-montage`."},"title":{"type":"string","maxLength":255,"minLength":1,"title":"Title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"category":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Category"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"},"priority":{"type":"integer","title":"Priority","description":"Sorts rows and carousels, highest first.","default":0},"premium":{"type":"boolean","title":"Premium","default":false},"status":{"type":"string","enum":["draft","published"],"title":"Status","description":"`published` makes the template public. Publishing needs every media URL in its composition to sit on the shared CDN (422 template_assets_not_shared) and no webhook_url (422 template_has_webhook).","default":"draft"},"preview_url":{"anyOf":[{"type":"string","maxLength":2083,"minLength":1,"format":"uri"},{"type":"null"}],"title":"Preview Url","description":"A finished render of the template."},"family":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Family","description":"Links the size variants of one design."}},"additionalProperties":false,"type":"object","required":["project_id","slug","title"],"title":"TemplateListingCreate"},"TemplateListingOut":{"properties":{"id":{"type":"string","title":"Id","description":"Listing id."},"project_id":{"type":"string","title":"Project Id","description":"The listed template project."},"slug":{"type":"string","title":"Slug"},"title":{"type":"string","title":"Title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"},"priority":{"type":"integer","title":"Priority"},"premium":{"type":"boolean","title":"Premium"},"status":{"type":"string","enum":["draft","published"],"title":"Status"},"preview_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Preview Url"},"published_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Published At"},"family":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Family"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","project_id","slug","title","priority","premium","status","created_at","updated_at"],"title":"TemplateListingOut","description":"A gallery listing as stored."},"TemplateListingUpdate":{"properties":{"slug":{"anyOf":[{"type":"string","maxLength":80,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$"},{"type":"null"}],"title":"Slug"},"title":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"category":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Category"},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags"},"priority":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Priority"},"premium":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Premium"},"status":{"anyOf":[{"type":"string","enum":["draft","published"]},{"type":"null"}],"title":"Status"},"preview_url":{"anyOf":[{"type":"string","maxLength":2083,"minLength":1,"format":"uri"},{"type":"null"}],"title":"Preview Url"},"family":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Family"}},"additionalProperties":false,"type":"object","title":"TemplateListingUpdate","description":"Send only the fields that change; null clears an optional field."},"TemplateOut":{"properties":{"id":{"type":"string","title":"Id","description":"The template's project id."},"scope":{"type":"string","enum":["public","workspace"],"title":"Scope","description":"`public` (in the gallery, from a listing) or `workspace` (one of your templates)."},"name":{"type":"string","title":"Name","description":"The listing title for a public template, else the project name."},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Listing copy. Null for workspace templates."},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category","description":"Listing category. Null for workspace templates."},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"},"width":{"type":"integer","title":"Width","description":"Canvas width in pixels."},"height":{"type":"integer","title":"Height","description":"Canvas height in pixels."},"aspect":{"type":"string","enum":["landscape","portrait","square"],"title":"Aspect","description":"From width and height."},"duration":{"type":"number","title":"Duration","description":"Composition length in seconds."},"element_count":{"type":"integer","title":"Element Count"},"thumbnail_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thumbnail Url","description":"Poster image; null until one is generated."},"version":{"type":"integer","title":"Version","description":"The head version the card describes."},"thumbnail_version":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Thumbnail Version","description":"The head version the thumbnail shows; stale while below `version`."},"preview_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Preview Url","description":"A finished render to play on hover. Public templates only for now."},"premium":{"type":"boolean","title":"Premium","default":false},"listing_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Listing Id","description":"The gallery listing, for public templates."},"slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slug","description":"The listing's stable handle: GET /v1/templates/{slug}."},"priority":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Priority","description":"Listing order, highest first."},"status":{"anyOf":[{"type":"string","enum":["draft","published"]},{"type":"null"}],"title":"Status","description":"Listing status. `draft` reaches admin workspaces only."},"family":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Family","description":"Links the size variants of one design."},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","scope","name","width","height","aspect","duration","element_count","version","created_at","updated_at"],"title":"TemplateOut","description":"One gallery card. Use ``id`` as ``source_project_id`` on POST /v1/projects, or as\n``template_id`` in an ``apply_template`` op."},"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.0,"minimum":0.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.0,"minimum":0.0},{"type":"null"}],"title":"Track","description":"Timeline track index (0–255). Informational only; not used by the renderer."},"time":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Time","description":"When this element starts on the output timeline, in seconds. Defaults to `0` when omitted.","example":0.0},"visible":{"type":"boolean","title":"Visible","description":"Set to `false` to skip this element without removing it from the request.","default":true},"locked":{"type":"boolean","title":"Locked","description":"Marks the element locked in editors. Not sent to the renderer.","default":false},"duration":{"type":"number","exclusiveMinimum":0.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.0},"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.0,"title":"Font Size","description":"Font size in pixels.","default":16,"example":48},"font_weight":{"type":"integer","maximum":900.0,"minimum":100.0,"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},"tabular_numbers":{"type":"boolean","title":"Tabular Numbers","description":"Tabular (monospaced) figures: every digit takes the same advance, so columns of numbers align across lines and a `counter` does not jitter as its value changes. The renderer's baked fonts carry the OpenType alternates; a font whose face lacks the feature gets synthesised tabular digits in the export only. Default `false` = the font's proportional figures.","default":false,"example":false},"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.0,"minimum":0.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},"background_style":{"type":"string","enum":["block","line_hard","line_round"],"title":"Background Style","description":"Shape of the `background` plate: `block` — one box behind the whole text; `line_hard` — a square-cornered box behind each line; `line_round` — a rounded box behind each line. Requires `background: true`. An animation that centres its words draws the `block` plate whichever style is set.","default":"block"},"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.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`, `preset` or `custom`) for structured timing control — but not both. Ignored when `word_animation` is set."}},"additionalProperties":false,"type":"object","required":["duration","text"],"title":"TextElement"},"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.0,"title":"Size","description":"Glow size, em-relative.","default":0.15},"intensity":{"type":"number","minimum":0.0,"title":"Intensity","description":"Glow intensity.","default":1.0}},"additionalProperties":false,"type":"object","required":["color"],"title":"TextGlow","description":"Soft glow around the glyphs (§4)."},"ThumbnailQueued":{"properties":{"job_id":{"type":"string","title":"Job Id","description":"The preview job rendering it. Poll via GET /v1/renders/{id}."},"project_version":{"type":"integer","title":"Project Version","description":"The head version the thumbnail will show."}},"type":"object","required":["job_id","project_version"],"title":"ThumbnailQueued","description":"A thumbnail on its way: the project's ``thumbnail_url`` updates when the job completes."},"TimeMap":{"properties":{"offset":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Offset","description":"Seconds into the children's timeline at the frame's start. Omitted: 0."},"speed":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Speed","description":"Rate of the children's timeline: 0 holds, below 0 plays backwards. Omitted: 1."},"loop":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Loop","description":"Seconds after which the children's timeline repeats. Omitted: no loop."},"freeze":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Freeze","description":"Hold the children at this many seconds into their timeline; overrides `offset` and `speed`."}},"additionalProperties":false,"type":"object","title":"TimeMap","description":"A composite frame's clock (AEC §5): the time its children read."},"TranscribeBody":{"properties":{"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 task.completed, task.failed, and progress events.","example":"https://app.example.com/hooks/framelane"},"source_url":{"type":"string","maxLength":2083,"minLength":1,"format":"uri","title":"Source Url"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","description":"BCP-47 language code (e.g. 'en'). Auto-detected when omitted."},"speech_model":{"type":"string","enum":["universal-2","universal-3-pro","nano"],"title":"Speech Model","description":"AssemblyAI speech model to use.","default":"universal-2"},"word_timings":{"type":"boolean","title":"Word Timings","description":"Include word-level timestamps in the output.","default":true},"speaker_labels":{"type":"boolean","title":"Speaker Labels","description":"Enable speaker diarisation (who spoke when).","default":false},"speakers_expected":{"anyOf":[{"type":"integer","maximum":10.0,"minimum":1.0},{"type":"null"}],"title":"Speakers Expected","description":"Hint for the expected number of speakers (used when speaker_labels=true)."},"sentiment_analysis":{"type":"boolean","title":"Sentiment Analysis","description":"Detect positive / neutral / negative sentiment per sentence.","default":false},"entity_detection":{"type":"boolean","title":"Entity Detection","description":"Identify named entities (people, organisations, places, etc.).","default":false},"auto_highlights":{"type":"boolean","title":"Auto Highlights","description":"Extract the most important phrases automatically.","default":false},"content_safety":{"type":"boolean","title":"Content Safety","description":"Flag sensitive content categories.","default":false},"iab_categories":{"type":"boolean","title":"Iab Categories","description":"Classify content into IAB taxonomy topics.","default":false},"word_boost":{"items":{"type":"string"},"type":"array","title":"Word Boost","description":"Domain-specific terms to boost recognition accuracy (max 200)."}},"additionalProperties":false,"type":"object","required":["source_url"],"title":"TranscribeBody"},"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.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.\n\nThe engine links transitions to video clips only — referencing an image,\ntext or other element type is rejected (it would crash the render node)."},"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:\n- `cross_dissolve` — Cross-dissolve — fades one clip into the next.\n- `dip_to_black` — Dips through black between clips.\n- `dip_to_white` — Dips through white between clips.\n- `wipe_left` — Wipes the next clip in from the right to the left.\n- `wipe_right` — Wipes the next clip in from the left to the right.\n- `wipe_up` — Wipes the next clip in from the bottom upward.\n- `wipe_down` — Wipes the next clip in from the top downward.\n- `diagonal_wipe` — Wipes across on a diagonal split.\n- `barn_doors_horizontal` — Two halves part horizontally like barn doors.\n- `barn_doors_vertical` — Two halves part vertically like barn doors.\n- `iris` — Circular iris (bullseye) open/close between clips.\n- `page_turn` — Page-flip / page-curl reveal.\n- `cross_zoom` — Zoom-blur push from one clip into the next.\n- `gradient_wipe` — Soft gradient-driven dissolve wipe.\n- `band_wipe` — Interleaved stripes wipe the next clip in.\n- `box_wipe` — Rectangular box grows to reveal the next clip.\n- `swirl` — Swirls the outgoing clip away in a spiral.\n- `glitch_memories` — Glitchy, memory-like distortion between clips.\n- `window_slice` — Slices the frame into sliding window strips.\n- `cube` — 3D cube rotation between the two clips.\n- `doorway` — Clips part like opening doors to reveal the next.\n- `pinwheel` — Pinwheel rotation wipe between clips.\n- `water_drop` — Rippling water-drop distortion reveals the next clip.\n- `crosshatch` — Crosshatch pattern dissolve between clips.\n- `dreamy` — Soft, dreamy blur-dissolve between clips.\n- `angular` — Angular wedge sweep between clips.\n- `burn` — Burning-film dissolve between clips.\n- `heart` — Heart-shaped iris reveal of the next clip.\n- `circle_open` — Expanding circle opens onto the next clip.\n- `color_phase` — Color-phase shift blends between clips.\n- `squares_wire` — Wireframe squares wipe between clips.\n- `whip_pan` — Directional motion-blur whip pan.\n- `cinematic_zoom` — Punch zoom with radial blur + chromatic fringing.\n- `burn_edge` — Noise-ridged burn front with ember glow and sparks.\n- `shatter` — Jittered shards fly apart revealing the incoming clip.\n- `domain_warp` — Organic noise-warped dissolve with an emissive frontier.\n- `circle_crop` — Both clips crop to a shrinking/growing circle through the cut.\n- `cross_warp` — Clips warp into each other along a dissolving threshold.\n- `fold_horizontal` — Outgoing clip folds away horizontally, revealing the incoming.\n- `fold_vertical` — Outgoing clip folds away vertically, revealing the incoming.\n- `linear_blur` — Cross-dissolve through a heavy directional blur peak.\n- `minimise_bottom_left` — Outgoing clip shrinks away into the bottom-left corner.\n- `minimise_bottom_right` — Outgoing clip shrinks away into the bottom-right corner.\n- `minimise_top_left` — Outgoing clip shrinks away into the top-left corner.\n- `minimise_top_right` — Outgoing clip shrinks away into the top-right corner.\n- `ripple` — Water-ripple distortion sweeps the cut across the frame.\n- `rotate` — Both clips rotate about the frame centre through the cut.\n- `splice` — Angled splice slides the incoming clip over the outgoing.\n- `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."}},"TrimElementOp":{"properties":{"op":{"type":"string","const":"trim_element","title":"Op"},"id":{"type":"string","title":"Id"},"in_point":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"In Point"},"out_point":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Out Point","description":"New source out-point. Video and audio carry no `duration` — their on-timeline footprint *is* (out_point − in_point)/speed — so a trim is a resize and the element's exit animations re-anchor to the new end."}},"additionalProperties":false,"type":"object","required":["op","id"],"title":"TrimElementOp"},"TrimKeyframe":{"properties":{"time":{"type":"number","minimum":0.0,"title":"Time","description":"Timeline seconds."},"start":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Start"},"end":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.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).\n\nAbsent ``start``/``end`` inherit the static ``trim`` values, so animating\n``end`` 0→1 on a stroked path draws it on while ``start`` stays put."},"TrimPath":{"properties":{"start":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Start","description":"Trim start, 0–1 along the path.","default":0},"end":{"type":"number","maximum":1.0,"minimum":0.0,"title":"End","description":"Trim end, 0–1 along the path.","default":1}},"additionalProperties":false,"type":"object","title":"TrimPath"},"UploadLinkOut":{"properties":{"id":{"type":"string","title":"Id","description":"The link id; poll `GET /v1/uploads/links/{id}` for the asset it produces.","example":"ul_01J8QR2K5VKDGN2T4FBM3CZYX7"},"url":{"type":"string","title":"Url","description":"Open this in a browser to upload one file into the workspace. Valid for one hour; the file's type and duration are taken from the file itself.","example":"https://framelane.io/upload/eyJhbGciOi…"},"expires_at":{"type":"string","format":"date-time","title":"Expires At","description":"When the link stops accepting uploads."}},"type":"object","required":["id","url","expires_at"],"title":"UploadLinkOut","description":"Response from POST /v1/uploads/links: a page a person can upload through."},"UploadLinkStatusOut":{"properties":{"id":{"type":"string","title":"Id","description":"The link id; poll `GET /v1/uploads/links/{id}` for the asset it produces.","example":"ul_01J8QR2K5VKDGN2T4FBM3CZYX7"},"url":{"type":"string","title":"Url","description":"Open this in a browser to upload one file into the workspace. Valid for one hour; the file's type and duration are taken from the file itself.","example":"https://framelane.io/upload/eyJhbGciOi…"},"expires_at":{"type":"string","format":"date-time","title":"Expires At","description":"When the link stops accepting uploads."},"asset_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Asset Id","description":"The asset the person uploaded through this link; null until they have."},"asset":{"anyOf":[{"$ref":"#/components/schemas/MediaAssetDetailOut"},{"type":"null"}],"description":"That asset's current state (`bytes_uploaded`, `ready`, duration), when present."}},"type":"object","required":["id","url","expires_at"],"title":"UploadLinkStatusOut","description":"Response from GET /v1/uploads/links/{id}: what the link has produced so far."},"UploadOut":{"properties":{"upload_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Upload Url","description":"GCS resumable upload URL. PUT your file bytes here with the correct Content-Type. **null** when you sent `content_base64`: the bytes are already stored, so there is no slot left to fill.","example":"https://storage.googleapis.com/framelane-prod-user/uploads/ws_01J.../a1b2c3.mp4?upload_id=..."},"source_url":{"type":"string","title":"Source Url","description":"CDN URL for the uploaded file. Pass this as source_url in render/task requests.","example":"https://cdn-user.framelane.io/uploads/ws_01J.../a1b2c3.mp4"},"expires_at":{"type":"string","format":"date-time","title":"Expires At","description":"When the upload_url expires (1 hour from now)."},"uploaded":{"type":"boolean","title":"Uploaded","description":"True when this call also stored the file (you sent `content_base64`), so `source_url` is usable immediately. False means only a slot was minted and the bytes are still yours to PUT.","default":false},"asset_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Asset Id","description":"The media asset registered for this upload — poll it with `GET /v1/workspace/assets/{id}`. Null for fonts, which are storable but are not timeline media and so get no asset row.","example":"asset_01J8QR2K5VKDGN2T4FBM3CZYX7"},"first_party_upload_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Party Upload Url","description":"The same upload session as `upload_url`, on a Framelane hostname. Use it when your network allows `*.framelane.io` but not `storage.googleapis.com` — same protocol, same chunks, same expiry. Null when you sent `content_base64`, or when this deployment has no first-party upload host.","example":"https://uploads.framelane.io/s/AAECAwQFBgcICQoL…"}},"type":"object","required":["source_url","expires_at"],"title":"UploadOut","description":"Response from POST /v1/uploads."},"UploadRequest":{"properties":{"content_type":{"type":"string","title":"Content Type","description":"MIME type of the file you will upload.","example":"video/mp4"},"filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filename","description":"The file's original name. Sets the stored object's extension and is kept as the asset's display name; without it the asset shows as its uuid key.","example":"my-clip.mp4"},"duration":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Duration","description":"Optional client-probed media duration in seconds — **video and audio only**; ignored for images, which have no duration. When provided, the asset is immediately usable/listable instead of waiting on server-side metadata extraction; the server probe (when it runs) overrides it.","example":12.5},"width":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Width","description":"Optional client-probed pixel width (video or image).","example":1920},"height":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Height","description":"Optional client-probed pixel height (video or image).","example":1080},"content_base64":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Base64","description":"The file's bytes, base64-encoded, to store in this same call. Use it when you cannot reach `storage.googleapis.com` — a sandboxed agent whose egress is allow-listed per host, typically. The response then carries `uploaded: true` and a null `upload_url`, and `source_url` is usable right away. Capped at 256 KB; larger files must use `upload_url`. A `data:` prefix, line wrapping and the URL-safe alphabet are all accepted."}},"additionalProperties":false,"type":"object","required":["content_type"],"title":"UploadRequest"},"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.0,"minimum":0.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.0,"title":"Start Time","description":"Seconds into the element to start the sweep.","default":0},"length":{"anyOf":[{"type":"number","exclusiveMinimum":0.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)."},"VersionResponse":{"properties":{"version":{"type":"string","title":"Version"},"git_sha":{"type":"string","title":"Git Sha"},"environment":{"type":"string","title":"Environment"},"render_node_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Render Node Version","description":"The render engine build (a git sha) the farm reported on its latest completed or cancelled render; null before one reports it."}},"type":"object","required":["version","git_sha","environment"],"title":"VersionResponse"},"VideoElement":{"properties":{"lut_url":{"anyOf":[{"type":"string","maxLength":2083,"minLength":1,"format":"uri"},{"type":"null"}],"title":"Lut Url"},"lut_intensity":{"type":"number","maximum":100.0,"minimum":0.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.0,"minimum":0.0,"title":"Crop Top","default":0},"crop_bottom":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Crop Bottom","default":0},"crop_left":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Crop Left","default":0},"crop_right":{"type":"number","maximum":1.0,"minimum":0.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","description":"Glass: the scene behind the element, Gaussian-blurred, fills its footprint under its translucent content (a shape's own silhouette, a video's or image's box with its corner radius and mask). A Gaussian sigma in canvas px, as CSS `backdrop-filter: blur()` takes it; the renderer caps it at 128. 0 is off.","default":0},"backdrop_blur_keyframes":{"anyOf":[{"items":{"$ref":"#/components/schemas/BlurKeyframe"},"type":"array","minItems":1},{"type":"null"}],"title":"Backdrop Blur Keyframes","description":"Animate `backdrop_blur` over the timeline; replaces the static value while present."},"layer_blur":{"type":"number","maximum":128.0,"minimum":0.0,"title":"Layer Blur","description":"Blurs the element itself as drawn: a Gaussian sigma in canvas px, as CSS `filter: blur()` takes it, spreading three sigma past its box. At most 128; 0 is off. Not on a shape yet (the editor's preview cannot draw it), and not with `blend_mode`, `backdrop_blur` or `matte`, beside which the renderer would draw the element without it.","default":0},"layer_blur_keyframes":{"anyOf":[{"items":{"$ref":"#/components/schemas/BlurKeyframe"},"type":"array","minItems":1},{"type":"null"}],"title":"Layer Blur Keyframes","description":"Animate `layer_blur` over the timeline; replaces the static value while present."},"glass":{"anyOf":[{"$ref":"#/components/schemas/Glass"},{"type":"null"}],"description":"A refracting glass edge: the scene behind bends inward near the element's outline. Pair it with `backdrop_blur` for frosted glass."},"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.0,"minimum":0.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.0,"minimum":0.0},{"type":"null"}],"title":"Track","description":"Timeline track index (0–255). Informational only; not used by the renderer."},"time":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Time","description":"When this element starts on the output timeline, in seconds. Defaults to `0` when omitted.","example":0.0},"visible":{"type":"boolean","title":"Visible","description":"Set to `false` to skip this element without removing it from the request.","default":true},"locked":{"type":"boolean","title":"Locked","description":"Marks the element locked in editors. Not sent to the renderer.","default":false},"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.0},{"type":"null"}],"title":"In Point","description":"In point — seconds into the source file to start playing from. Defaults to `0`.","example":0.0},"out_point":{"anyOf":[{"type":"number","exclusiveMinimum":0.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.0},"speed":{"type":"number","maximum":4.0,"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.0,"example":1.0},"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.0,"minimum":0.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},"muted":{"type":"boolean","title":"Muted","description":"Silence the clip's embedded audio. `volume` is kept, so unmuting restores the level.","default":false},"loop":{"type":"boolean","title":"Loop","description":"Repeat the source to fill the clip: when playback passes the end of the file it restarts from the file's first frame (not `in_point`). Set `out_point` past the source's length to choose how long the clip runs — its timeline length is still `(out_point − in_point) / speed`. Requires `out_point`.","default":false},"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"},"Violation":{"properties":{"code":{"type":"string","title":"Code","description":"Stable machine code, e.g. 'DANGLING_TRANSITION'."},"message":{"type":"string","title":"Message","description":"Human-readable explanation."},"severity":{"$ref":"#/components/schemas/Severity","description":"'error' blocks a good render; 'warning' is advisory.","default":"error"},"path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path","description":"Where it happened, e.g. 'elements[2]' or 'transitions[0].from_id'."},"pointer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pointer","description":"The same place as an RFC 6901 JSON pointer into the request, e.g. '/elements/2/children/0/text_color'."},"target_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Id","description":"Id of the element, frame, instance or group the finding is about; inside an instance, its expanded path (e.g. 'card1/title'). Null when it names none."},"bbox":{"anyOf":[{"items":{"type":"number"},"type":"array"},{"type":"null"}],"title":"Bbox","description":"Canvas-space box [x, y, w, h] in pixels for layout findings (B1); null otherwise."},"time":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Time","description":"Timeline second at which the finding applies; null for whole-timeline findings."}},"type":"object","required":["code","message"],"title":"Violation"},"WebhookCreate":{"properties":{"url":{"type":"string","maxLength":2083,"minLength":1,"format":"uri","title":"Url"},"events":{"items":{"$ref":"#/components/schemas/WebhookEvent"},"type":"array","title":"Events"},"enabled":{"type":"boolean","title":"Enabled","default":true}},"additionalProperties":false,"type":"object","required":["url"],"title":"WebhookCreate"},"WebhookCreated":{"properties":{"id":{"type":"string","title":"Id"},"url":{"type":"string","title":"Url"},"events":{"items":{"type":"string"},"type":"array","title":"Events"},"enabled":{"type":"boolean","title":"Enabled"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"secret":{"type":"string","title":"Secret"}},"type":"object","required":["id","url","events","enabled","created_at","updated_at","secret"],"title":"WebhookCreated","description":"Returned at create-time and after rotate-secret -- includes plaintext secret."},"WebhookDeliveryOut":{"properties":{"id":{"type":"string","title":"Id"},"delivery_id":{"type":"string","title":"Delivery Id"},"webhook_id":{"type":"string","title":"Webhook Id"},"job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Job Id"},"event_type":{"type":"string","title":"Event Type"},"attempt_number":{"type":"integer","title":"Attempt Number"},"status":{"type":"string","title":"Status"},"status_code":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Status Code"},"response_body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Response Body"},"attempted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Attempted At"},"next_retry_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Next Retry At"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","delivery_id","webhook_id","event_type","attempt_number","status","created_at"],"title":"WebhookDeliveryOut"},"WebhookEvent":{"type":"string","enum":["render.queued","render.processing","render.completed","render.failed","render.cancelled","task.queued","task.processing","task.completed","task.failed","task.cancelled","asset.ready"],"title":"WebhookEvent","description":"A job or asset lifecycle event a webhook subscription can receive.\n\nValues:\n- `render.queued` — A render job was accepted and queued.\n- `render.processing` — A render job started processing.\n- `render.completed` — A render job finished successfully.\n- `render.failed` — A render job failed.\n- `render.cancelled` — A render job was cancelled.\n- `task.queued` — A task job was accepted and queued.\n- `task.processing` — A task job started processing.\n- `task.completed` — A task job finished successfully.\n- `task.failed` — A task job failed.\n- `task.cancelled` — A task job was cancelled.\n- `asset.ready` — An external source finished ingesting into Framelane storage.","x-enumDescriptions":{"render.queued":"A render job was accepted and queued.","render.processing":"A render job started processing.","render.completed":"A render job finished successfully.","render.failed":"A render job failed.","render.cancelled":"A render job was cancelled.","task.queued":"A task job was accepted and queued.","task.processing":"A task job started processing.","task.completed":"A task job finished successfully.","task.failed":"A task job failed.","task.cancelled":"A task job was cancelled.","asset.ready":"An external source finished ingesting into Framelane storage."}},"WebhookOut":{"properties":{"id":{"type":"string","title":"Id"},"url":{"type":"string","title":"Url"},"events":{"items":{"type":"string"},"type":"array","title":"Events"},"enabled":{"type":"boolean","title":"Enabled"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","url","events","enabled","created_at","updated_at"],"title":"WebhookOut"},"WebhookRotateResponse":{"properties":{"secret":{"type":"string","title":"Secret"}},"type":"object","required":["secret"],"title":"WebhookRotateResponse"},"WebhookUpdate":{"properties":{"url":{"anyOf":[{"type":"string","maxLength":2083,"minLength":1,"format":"uri"},{"type":"null"}],"title":"Url"},"events":{"anyOf":[{"items":{"$ref":"#/components/schemas/WebhookEvent"},"type":"array"},{"type":"null"}],"title":"Events"},"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Enabled"}},"additionalProperties":false,"type":"object","title":"WebhookUpdate"},"Word":{"properties":{"text":{"type":"string","title":"Text"},"start":{"type":"number","minimum":0.0,"title":"Start","description":"ABSOLUTE output-timeline seconds (not element-relative)."},"end":{"type":"number","exclusiveMinimum":0.0,"title":"End","description":"ABSOLUTE output-timeline seconds (not element-relative)."}},"additionalProperties":false,"type":"object","required":["text","start","end"],"title":"Word"},"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"},"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).\n\nValues:\n- `glow` — Highlights the active word with a glowing emphasis.\n- `box` — Draws a colored box behind the active word.\n- `scale_pop` — Pops the active word with a quick scale punch.\n- `slide_up` — Floats each word up into place as it becomes active.\n- `fly_in` — Drops each word in from above as it becomes active.\n- `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)."}},"WordSpec":{"properties":{"word":{"type":"string","minLength":1,"title":"Word","description":"The word (whole-word match; UTF-8)."},"start":{"type":"number","minimum":0.0,"title":"Start","description":"Word reveal start, seconds — drives a word-group animation; ignored without one."},"end":{"type":"number","exclusiveMinimum":0.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).\n\n``style`` overrides are static for the element's whole life. ``start``/\n``end`` drive **word-group animations only** — bind one via\n``word_animation`` (or a word-group ``animation_preset``) or the timings\nhave nothing to time and are ignored by the renderer."},"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."},"WorkspaceOut":{"properties":{"id":{"type":"string","title":"Id","description":"Unique workspace ID.","example":"ws_01J8QR2K5VKDGN2T4FBM3CZYX8"},"name":{"type":"string","title":"Name","description":"Display name for the workspace.","example":"Acme Corp"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"Contact email for the workspace owner.","example":"dev@acmecorp.com"},"email_verified":{"type":"boolean","title":"Email Verified","description":"Whether the email address has been verified via OTP.","default":false},"plan":{"$ref":"#/components/schemas/WorkspacePlan","description":"Current billing plan: `free`, `pro`, or `enterprise`.","default":"free","example":"free"},"plan_status":{"$ref":"#/components/schemas/WorkspacePlanStatus","description":"Billing status: `active`, `past_due`, `cancelled`, or `trialing`.","default":"active","example":"active"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"ISO-8601 UTC timestamp when the workspace was created."},"is_admin":{"type":"boolean","title":"Is Admin","description":"Whether this is an admin workspace, which curates the public template gallery (POST, PATCH and DELETE /v1/templates) and sees its draft listings."}},"type":"object","required":["id","name","created_at"],"title":"WorkspaceOut"},"WorkspacePlan":{"type":"string","enum":["free","pro","enterprise"],"title":"WorkspacePlan"},"WorkspacePlanStatus":{"type":"string","enum":["active","past_due","cancelled","trialing"],"title":"WorkspacePlanStatus"},"WorkspaceUpdate":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"}},"additionalProperties":false,"type":"object","required":["name"],"title":"WorkspaceUpdate"},"WorkspaceUsage":{"properties":{"period_start":{"type":"string","format":"date-time","title":"Period Start","description":"Start of the requested usage period (first of month if no `period` was given)."},"period_end":{"type":"string","format":"date-time","title":"Period End","description":"Exclusive end of the requested usage period (now, if `period` was given)."},"render_minutes":{"type":"number","title":"Render Minutes","description":"Total render output duration in minutes for the period.","example":142.7},"render_minutes_limit":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Render Minutes Limit","description":"Monthly render-minute cap for FREE-plan workspaces, on the default calendar-month billing view (`period` omitted). `null` for a paid (unmetered) plan, or when a rolling `period` was requested — the cap resets by calendar month, so it isn't a meaningful ceiling for an arbitrary rolling window.","example":30.0},"renders_count":{"type":"integer","title":"Renders Count","description":"Number of billable renders (non-preview, completed) submitted during the period.","example":12},"task_count":{"type":"integer","title":"Task Count","description":"Total AI tasks submitted during the period.","example":384},"egress_bytes":{"type":"integer","title":"Egress Bytes","description":"Total artifact egress bytes for the period.","example":5368709120}},"type":"object","required":["period_start","period_end","render_minutes","renders_count","task_count","egress_bytes"],"title":"WorkspaceUsage"},"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"},"ApiErrorCode":{"description":"Machine-readable error code returned on a 4xx/5xx response. Branch on `error.code` to\nhandle failures programmatically.\n\nValues:\n- `invalid_request` — The request was malformed or failed validation.\n- `unauthorized` — Missing or invalid API key.\n- `forbidden` — Authenticated but not permitted to perform this action.\n- `not_found` — The requested resource does not exist.\n- `conflict` — Conflicts with current state (e.g. a reused Idempotency-Key with a different body).\n- `unsupported_feature` — The composition uses a feature the renderer does not support.\n- `invalid_source` — A source URL is malformed or unreachable.\n- `source_not_found` — A referenced source file could not be found.\n- `source_too_large` — A source file exceeds the allowed size.\n- `asset_not_ready` — A referenced asset is still ingesting and is not ready yet.\n- `ingest_failed` — Copying an external source into Framelane storage failed.\n- `codec_unsupported` — A source uses a codec the renderer cannot decode.\n- `quota_exceeded` — The workspace has exhausted its plan quota (HTTP 402).\n- `rate_limited` — Too many requests; retry after the Retry-After interval.\n- `internal` — An unexpected server error occurred.\n- `email_not_verified` — The workspace email is unverified; verify it before making authed calls.\n- `email_in_use` — The email is already associated with another workspace.\n- `invalid_otp` — The verification code is incorrect or expired.\n- `unsupported_content_type` — The provided content type is not supported.\n- `plan_required` — The feature needs a higher plan (HTTP 402); `details.required_plan` names it.\n- `admin_only` — Only an admin workspace may call this route (HTTP 403).\n- `template_assets_not_shared` — A published template may reference only shared CDN media; `details.urls` lists the rest.\n- `template_has_webhook` — A published template may not carry a `webhook_url`: every copy of it would notify that URL.\n- `http_error` — A generic HTTP error not covered by a more specific code.\n- `shader_compile_error` — A shader's SkSL did not compile; `details.errors` gives each error's line and column.\n- `shader_compiler_unavailable` — The shader compiler is not installed on this deployment or did not answer in time (HTTP 503).\n- `shader_not_found` — An effect names a shader id that is neither this workspace's nor shared.\n- `shader_param_invalid` — An effect sets a shader param the shader does not declare, or a value of the wrong type or range.","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","inline_too_large","plan_required","admin_only","template_assets_not_shared","template_has_webhook","http_error","shader_compile_error","shader_compiler_unavailable","shader_not_found","shader_param_invalid"],"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.","plan_required":"The feature needs a higher plan (HTTP 402); `details.required_plan` names it.","admin_only":"Only an admin workspace may call this route (HTTP 403).","template_assets_not_shared":"A published template may reference only shared CDN media; `details.urls` lists the rest.","template_has_webhook":"A published template may not carry a `webhook_url`: every copy of it would notify that URL.","http_error":"A generic HTTP error not covered by a more specific code.","shader_compile_error":"A shader's SkSL did not compile; `details.errors` gives each error's line and column.","shader_compiler_unavailable":"The shader compiler is not installed on this deployment or did not answer in time (HTTP 503).","shader_not_found":"An effect names a shader id that is neither this workspace's nor shared.","shader_param_invalid":"An effect sets a shader param the shader does not declare, or a value of the wrong type or range."}},"ApiError":{"description":"The error envelope returned on every 4xx/5xx response: `{\"error\": {...}}`.","properties":{"error":{"$ref":"#/components/schemas/ErrorResponse"}},"required":["error"],"title":"ApiError","type":"object"}},"securitySchemes":{"ApiKey":{"type":"http","scheme":"bearer"}}},"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":"templates","description":"Browse the template gallery, and manage the public catalogue (admin workspaces)."},{"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."}]}