{"version":"2026.09","base_url":"https://splice.film.fun","authentication":{"mechanisms":["Authorization: Bearer <agent_key>","x-api-key: <agent_key>"],"key_provisioning":"https://splice.film.fun/dashboard/api-keys","idempotency_header":"Idempotency-Key","trace_header":"X-Trace-Id"},"reference_schemes":{"item_ref":{"pattern":"item:<production_item_id>","scope":"any URL-shaped string in a generator body","applies_to":"routes whose reference_schemes include item_ref: /transcribe, /generate/shot-framing and most /generate/* and /edit/* routes. Not resolved on /generate/{image,video,video-animate,voice,music,vision-language} or /edit/{image,image-upscale,media-merge} — pass media_url there.","on_unknown":400},"entity_slug":{"pattern":"@<slug>","slug_format":"^[a-z0-9_-]+$","scope":"shot-framing prompt field (server) + ShotFramingGenerator (SDK)","unique_per":"production","on_unknown":400}},"error_codes":{"unauthorized":"Missing or invalid API key (401)","forbidden":"API key lacks the required scope (403)","invalid_request":"Validation failed (400)","not_found":"Resource not found or not accessible (404)","conflict":"Idempotency conflict or state conflict (409)","idempotency_in_progress":"A request with the same Idempotency-Key is still running; retry after Retry-After (409)","idempotency_outcome_unknown":"The first request with this Idempotency-Key may have been charged, so it will not run again; look the key up or send a new one (409)","idempotency_unavailable":"The Idempotency-Key store is unavailable; retry shortly (503)","insufficient_credits":"Not enough credits: {error: {code, message}, required?, balance?, shortfall?} (402)","not_cancellable":"Not a generation, or its job already finished (409)","cancel_unavailable":"Job cancel is switched off; see features.cancel (501)","quote_exceeds_max_credits":"The quote is higher than the max_credits you passed; nothing ran (409)","quote_unavailable":"Quotes are switched off; see features.quote (501)","upstream_error":"Downstream (Forge) returned an error (502)","unavailable":"Service unavailable (503)","service_unavailable":"Upstream unreachable or timed out (503)","payment_required":"Payment needed before the request can run (402)","unprocessable_entity":"Well-formed but rejected input (422)","unsupported_media_type":"Wrong Content-Type for the route (415)","not_implemented":"Not available on this deployment (501)","rate_limited":"Too many requests (429)","internal_error":"Unexpected failure (500)"},"scopes":{"*":"All scopes — grant with care","session:read":"Read session / default production","balance:read":"Read credits","credits:write":"Transfer + purchase credits","productions:read":"List / fetch productions, items, moodboards, shotboards, tasks","productions:write":"Create / update / delete productions + nested resources","entities:read":"List / fetch characters, locations, props, entity slugs","entities:write":"Create + import entities, assign entity slugs","generate:image":"POST /api/agents/generate/image","generate:video":"POST /api/agents/generate/video (and video-animate, video-image-audio)","generate:audio":"POST /api/agents/generate/{voice,voice-changer,music,sfx,auto-sfx,lipsync}","generate:sheet":"POST /api/agents/generate/{character,location,prop,set}-sheet","generate:shot-framing":"POST /api/agents/generate/shot-framing","generate:other":"POST /api/agents/generate/{timeline-export,vision-language}","generate:composition":"POST /api/agents/generate/composition-render — render a Hyperframes HTML composition to MP4","compositions:read":"GET /api/agents/compositions[/{id}] — list / fetch Motion (Hyperframes) compositions; GET /{id}/revisions — version history; GET /api/agents/productions/{id}/compositions[/{compositionId}] — a production's compositions, one with its HTML + parsed layer summary + version","compositions:write":"POST / PATCH / DELETE /api/agents/compositions — create / update / delete / duplicate compositions; POST /{id}/revisions/revert — restore an earlier revision; PATCH /api/agents/productions/{id}/compositions/{compositionId} — edit layers with structured ops or replace the HTML (version-checked)","edit:media":"POST /api/agents/edit/*","transcribe:write":"POST /api/agents/transcribe","tools:read":"GET /api/agents/tools/*","studio:read":"Read studio projects + render status","studio:write":"Create studio projects + exports + handoff URLs; publish to film.fun Studio (publish-to-studio) and set the production's Studio link (studio-link PUT)","share:read":"Read share links + comments + decisions (split-approval pattern)","share:write":"Create / update / delete share links — publish for human review or present"},"routes":[{"path":"/api/agents/session","methods":["GET"],"description":"Default session + creation/production id","scope":"session:read"},{"path":"/api/agents/idempotency/[key]","methods":["GET"],"description":"Look up a generate/edit request by its Idempotency-Key (the caller's own keys only; another account's key is a 404). Returns {status: pending|completed|failed, stale?, retryable?, outcome?, item_id, job_id, production_id, response_status, response}. 404 when unknown or expired. Any valid key; no extra scope."},{"path":"/api/agents/balance","methods":["GET"],"description":"Credit summary","scope":"balance:read"},{"path":"/api/agents/credits/transfer","methods":["POST"],"description":"Transfer credits","scope":"credits:write"},{"path":"/api/agents/credits/packs","methods":["GET"],"description":"Credit packs with their price now in each Solana token: { packs: [{ id, name, credits, price_usd, credits_per_usd, quotes: { USDC|SOL|AUDD: { amount, amount_atomic, decimals, mint } } }], tokens }. The purchase 402 is the binding quote.","scope":"balance:read"},{"path":"/api/agents/credits/purchase","methods":["POST"],"description":"Buy a credit pack with Solana (USDC, SOL or AUDD). Body { pack_id: starter|creator|pro|studio, payment_token? }. Without X-PAYMENT it returns 402 payment_required with transaction_id, receiver_address, amount, token_mint and solana_pay { url, reference, status_url }: pay the Solana Pay url from any wallet (or show it as a QR code) and poll status_url, or send the transfer yourself and retry with X-PAYMENT { transaction_id, signature, payer_address }. See features.credits.","scope":"credits:write"},{"path":"/api/agents/credits/purchase/[transactionId]","methods":["GET"],"description":"Status of a credit purchase. For Solana Pay it finds the payment on chain by the purchase reference, verifies token, receiver, amount and reference, and applies the credits. Returns { status: pending|completed, credits_applied?, signature?, pack }. Safe to poll.","scope":"credits:write"},{"path":"/api/agents/manifest.json","methods":["GET"],"description":"This manifest"},{"path":"/api/agents/productions","methods":["GET","POST"],"description":"List / create productions. GET supports optional ?series_id= filter — when present, returns only productions in that series ordered by episode_number ASC. Each row carries series_id + episode_number for client-side grouping.","scope":"productions:read"},{"path":"/api/agents/series","methods":["GET"],"description":"Discovery: distinct series_id values across the agent's editable productions, with episode_count + max_episode_number + latest_created_at per series. Sorted by latest_created_at descending. No standalone series table in Splice — this is a GROUP BY over splice_productions; richer series metadata (title, cover, etc.) lives in Studio.","scope":"productions:read"},{"path":"/api/agents/productions/[id]","methods":["GET","PATCH","DELETE"],"description":"Single production — [id] = creation_id. PATCH accepts: title, description, status, video_url, cover_image, thumbnail, duration_seconds, aspect_ratio, content_rating, genre, tags, metadata, series_id (text), episode_number (int); other keys are ignored (ff_production_id is server-owned). The last two are episodic series ordering — see /api/agents/series for discovery.","scope":"productions:read"},{"path":"/api/agents/productions/[id]/items","methods":["GET"],"description":"List production items","scope":"productions:read"},{"path":"/api/agents/productions/[id]/items/[itemId]","methods":["GET"],"description":"Get one item by id, fetched directly (any item in the production, not just the newest 50): id, status, media_url, job_id, job_status, cancelled?, error_message?, answer? (vision-language text output). A pending item whose job failed, timed out or was cancelled reports status \"failed\".","scope":"productions:read"},{"path":"/api/agents/productions/[id]/items/[itemId]/cancel","methods":["POST"],"description":"Cancel a pending/processing generation. Forge marks the job canceled and refunds it; returns {cancelled, refunded, refund_amount, job_status}. 409 not_cancellable when the item is not a generation or already finished. 501 cancel_unavailable while features.cancel.enabled is false.","scope":"productions:write"},{"path":"/api/agents/productions/[id]/media","methods":["POST"],"description":"Upload media: multipart/form-data with the file in \"file\" (max 4.5 MB); optional name, type, tags (JSON array), metadata (JSON object). Returns {media_item: {id, media_url, …}}. Other content types get 415 — pass hosted media to a generator by URL instead.","scope":"productions:write"},{"path":"/api/agents/productions/[id]/moodboards","methods":["GET","POST"],"description":"List or create moodboards (visual reference boards). POST body: {title, description?, canvas?} — `canvas` is the structured board content (positioned cards, items, text). GET → productions:read; POST → productions:write.","scope":"productions:write"},{"path":"/api/agents/productions/[id]/shotboards","methods":["GET","POST"],"description":"Shotboards (shown as \"Assembly\" in the UI)","scope":"productions:read"},{"path":"/api/agents/productions/[id]/tasks","methods":["GET","POST"],"description":"Tasks","scope":"productions:read"},{"path":"/api/agents/productions/[id]/studio/projects","methods":["GET","POST"],"description":"Studio projects","scope":"studio:read"},{"path":"/api/agents/productions/[id]/studio/projects/[projectId]/export","methods":["POST","GET"],"description":"Export / render status","scope":"studio:write"},{"path":"/api/agents/productions/[id]/studio-handoff","methods":["GET"],"description":"Browser handoff URL","scope":"studio:read"},{"path":"/api/agents/characters","methods":["GET","POST"],"description":"Client library — characters. POST creates; production-scoped route links.","scope":"entities:read | entities:write","returns_201_on_success":true},{"path":"/api/agents/characters/[characterId]","methods":["GET","PUT"],"description":"Fetch / update one client-library character. PUT sets fields like sheet_url / avatar_url (e.g. attach a generated portrait or character sheet as the reference image @slug routes into shot-framing).","scope":"entities:read | entities:write"},{"path":"/api/agents/locations","methods":["GET","POST"],"description":"Client library — locations. POST creates; production-scoped route links.","scope":"entities:read | entities:write","returns_201_on_success":true},{"path":"/api/agents/props","methods":["GET","POST"],"description":"Client library — props. POST creates; production-scoped route links.","scope":"entities:read | entities:write","returns_201_on_success":true},{"path":"/api/agents/productions/[id]/characters","methods":["GET","POST"],"description":"Production-scoped characters (import + list)","scope":"entities:read"},{"path":"/api/agents/productions/[id]/locations","methods":["GET","POST"],"description":"Production-scoped locations","scope":"entities:read"},{"path":"/api/agents/productions/[id]/props","methods":["GET","POST"],"description":"Production-scoped props","scope":"entities:read"},{"path":"/api/agents/productions/[id]/entity-slugs","methods":["GET","POST","DELETE"],"description":"@slug CRUD for production entities","scope":"entities:write"},{"path":"/api/agents/productions/[id]/universe","methods":["GET","POST"],"description":"Universe Bible — lore / themes / era / factions / style guide that wraps the production's entities","scope":"productions:write"},{"path":"/api/agents/productions/[id]/universe-bundle","methods":["GET"],"description":"One-shot read: Universe Bible + all production characters/locations/sets/props","scope":"entities:read"},{"path":"/api/agents/productions/[id]/draft-pipeline","methods":["POST"],"description":"One pitch drafts the PLAN half of the pipeline: generates a 60-second script, creates it, then chains the stage bridges (script→storyboard beats→shotlist shots). Body: {pitch, title?}. Returns {script, storyboard, shotlist, counts}. ~1 minute of sequential LLM work — use a generous timeout. The one-call opening move for autonomous productions.","scope":"productions:write"},{"path":"/api/agents/productions/[id]/scripts","methods":["GET","POST"],"description":"List or create persisted scripts for a production. GET forwards limit/offset/status/sort_direction. POST body: {title, format?} — format one of fountain/plaintext/markdown/fdx; returns 201. Used by agents that decompose narratives into shot sheets or shotlists. GET → productions:read; POST → productions:write.","scope":"productions:write","returns_201_on_success":true},{"path":"/api/agents/productions/[id]/scripts/[scriptId]","methods":["GET"],"description":"Read a single script — full body (logline, synopsis, scenes, dialogue) + metadata.","scope":"productions:read"},{"path":"/api/agents/productions/[id]/shotlists","methods":["GET","POST"],"description":"List or create shot lists for a production. GET filters: limit/offset/status/script_id/storyboard_id/sort_direction. POST body: {title, description?, script_id?, storyboard_id?, source_type_default?, scenes?, status?, metadata?} — Forge normalises ids/order/source_type/status server-side.","scope":"productions:write"},{"path":"/api/agents/productions/[id]/shotlists/[shotlistId]","methods":["GET","PUT"],"description":"Read or replace fields on a shot list. PUT accepts partial body — only fields sent are updated. Updating scenes is the path agents use after decomposing a script into shot rows.","scope":"productions:write"},{"path":"/api/agents/productions/[id]/storyboards","methods":["GET","POST"],"description":"List or create storyboards for a production. Storyboards are the visual planning layer between script and shotlist (each storyboard = sequence of beats, each beat = ordered reference frames). GET filters: limit/offset/status/script_id/sort_direction. POST body: {title, description?, script_id?, aspect_ratio?, beats?, status?, metadata?} — Forge normalises beat ids/order server-side.","scope":"productions:write"},{"path":"/api/agents/productions/[id]/storyboards/[storyboardId]","methods":["GET","PUT"],"description":"Read or replace fields on a storyboard. PUT accepts partial body — only fields sent are updated.","scope":"productions:write"},{"path":"/api/agents/productions/[id]/takes","methods":["GET","POST"],"description":"List or create takes for the shoot stage. One shotlist shot can have many takes (live-action / composition / stock captures). GET filters: limit/offset/status/shotlist_id/shot_id/shoot_label/sort_direction. POST body requires {shotlist_id, shot_id} + optional {shoot_label, take_number, status, asset_url, asset_item_id, thumbnail_url, duration_s, captured_at, notes, metadata}.","scope":"productions:write"},{"path":"/api/agents/productions/[id]/takes/[takeId]","methods":["GET","PUT","DELETE"],"description":"Read, update, or archive a single take. Status flow: planned → rolling → captured → circle | no_good → archived.","scope":"productions:write"},{"path":"/api/agents/productions/[id]/stages","methods":["GET"],"description":"Aggregated production stage state — counts across moodboards/scripts/storyboards/shotlists/takes/studio + universe bible flag. Designed for polling.","scope":"productions:read"},{"path":"/api/agents/productions/[id]/readiness","methods":["GET"],"description":"Decision-helper that wraps /stages with a suggested_next_steps array ordered by the canonical pipeline (universe → moodboard → script → storyboard → shotlist → shoot → edit). Each suggestion carries a recommended_action mapped to a SpliceMediaClient method so agents can dispatch directly.","scope":"productions:read"},{"path":"/api/agents/productions/[id]/shot-sheet","methods":["GET","PATCH"],"description":"Shot Sheet aggregator. GET ?shotlist_id=X returns shotlist + universe-bundle for rendering. PATCH ?shotlist_id=X with body {shotsheet:{...}} partial-merges fields into shotlist.metadata.shotsheet (color palette, mood keywords, audio/cinematography notes). Sister to /shot-framing.","scope":"productions:write"},{"path":"/api/agents/productions/[id]/shot-sheet/stills","methods":["POST"],"description":"Generate the still frame for one shot in a Shot Sheet. POST ?shotlist_id=X&shot_id=Y composes a shot-framing body from the scene context (characters/location refs + shot description as prompt), fires the generator, and stamps asset_item_id onto the shot. Returns production_item_id for polling.","scope":"generate:shot-framing"},{"path":"/api/agents/productions/[id]/shot-sheet/seedance","methods":["POST"],"description":"Send one shot in a Shot Sheet to Seedance 2 video generation. POST ?shotlist_id=X&shot_id=Y composes the video body from scene identity refs (character sheets), environment (location), motion priors (entity gallery videos), audio (voice samples + reference tracks). Defaults to seedance2Fast: with voice samples the take is voiced (the still becomes a reference image); without them the still is the i2v first frame; with no still, text-to-video. first_frame_lock keeps the still as the first frame. The response reports input_mode. Closes the ShotFramer → ShotSheet → VideoGenerator loop.","scope":"generate:video"},{"path":"/api/agents/productions/[id]/shot-sheet/stills/all","methods":["POST"],"description":"Bulk: generate stills for every shot in a Shot Sheet in parallel. POST ?shotlist_id=X. Per-shot failures isolate; successes are persisted in a single shotlist PUT. Returns array of { shot_id, production_item_id, error? }.","scope":"generate:shot-framing"},{"path":"/api/agents/productions/[id]/shot-sheet/seedance/all","methods":["POST"],"description":"Bulk: generate video for every shot in a Shot Sheet (or shot_ids), one shot at a time. POST ?shotlist_id=X. A shot's dialogue [{ character_id | speaker, line }] is said in each speaking character's voice: rendered in that voice, then passed as reference audio (seedance2Fast, seedance25, minimaxH3) or lip-synced (other models); durations follow the lines' syllable estimate. Unchanged steps reuse their takes (0 credits); regenerate: [shot ids | step keys] forces new ones. dry_run: true returns the itemised quote only; max_credits refuses a higher quote (409 quote_exceeds_max_credits). Returns shots[{ shot_id, production_item_id, prompt, input_mode, voice_path, first_frame_image, reused, status, error? }], successes, failures, reused, pending (call again to continue), dispatched_credits, stopped, quote.","scope":"generate:video"},{"path":"/api/agents/productions/[id]/shot-sheet/from-script","methods":["GET"],"description":"One-shot composer for \"decompose this script\" agent loops. GET ?script_id=X returns the full script + universe-bundle + a structured prompt template + scaffolding hints (suggested titles, available entity ids). The agent does the LLM decomposition (it IS an LLM); then calls create_shotlist with the result.","scope":"productions:read"},{"path":"/api/agents/productions/[id]/shot-sheet/cost-preview","methods":["GET"],"description":"Count-only cost preview for the bulk shot-sheet generators. GET ?shotlist_id=X returns shot_count + bulk_stills_targets + bulk_seedance_targets + i2v/t2v split — no pricing, just the *targets* that bulk calls would fire. Combine with /api/agents/tools/generator-tools/{id}/models to estimate spend before pulling the trigger.","scope":"productions:read"},{"path":"/api/agents/productions/[id]/quote","methods":["POST"],"description":"Itemised quote before a multi-step job; spends nothing. Body { shotlist_id, shot_ids?, model_id?, resolution?, duration_seconds?, regenerate?, reuse? } prices what /shot-sheet/seedance/all would run (dialogue voice lines in each speaker's voice, the video, lip-sync where the model takes no reference audio); or { steps: [{ key, generator, tool_id, params }] } with \"$ref:<key>\" for another step's output. Returns { quote: { lines[{ key, status generate|reused|in_progress|not_needed, full_credits, credits, input_hash, reuse_item_id }], shots[], total_credits, saved_credits, balance, sufficient } }. Unchanged steps reuse their takes (0 credits). Priced by Forge with the charge's own pricing. See features.quote.","scope":"productions:read"},{"path":"/api/agents/productions/[id]/reference-analysis","methods":["POST"],"description":"Read a reference video for its STRUCTURE and get back a timed shot plan — pacing, shot lengths, beats and framing, never the reference's footage, audio or artwork. Body { media_item_id | video_url, depth? quick|standard|deep, language?, time_range?, brief?, aspect_ratio?, rights: { attested, statement_version }, recast?: [{ subject_role, entity_id, entity_name, entity_slug?, kind }], create_shotlist?, shotlist_title?, dry_run?, max_credits?, reuse? }. `rights` is required and recorded before anything runs (400 reference_rights_required without it); a real person's face or voice still needs that character's own consent record. Three paid steps run in order — transcribe, analyse (flat 3 credits), plan — each quoted with the charge's own pricing right before it runs and reused at 0 credits when an identical read exists. Returns { analysis_id, status, summary, analysis, plan, casting_call, uncast, shotlist_id, credits_spent, steps, stopped }. See features.remix.","scope":"generate:other","idempotent":true},{"path":"/api/agents/productions/[id]/variants","methods":["POST"],"description":"Make variants of one shotlist that each change exactly ONE thing. Body { shotlist_id, axis: hook_line|cast|language|aspect|music, variants: [{ key, label?, value }], include_base?, model_id?, resolution?, aspect_ratio?, duration_seconds?, regenerate?, reuse?, dry_run?, max_credits? }. Every arm is planned by the same planner a batch uses, so a shot the axis didn't touch keeps its input hash and reuses the existing take at 0 credits — only what changed is paid for. The quote reports, per arm, how many shots are generated and how many are reused. dry_run returns the quote only; max_credits refuses a higher one (409 quote_exceeds_max_credits). Shots run one at a time against the account-wide limit; leftovers come back as pending, and calling again reuses what finished. Returns { batch_id, axis, variants[{ key, label, shots, credits, dispatched, reused, pending }], quote, dispatched_credits, pending, stopped }. See features.variants.","scope":"generate:video","idempotent":true},{"path":"/api/agents/productions/[id]/transcriptions","methods":["GET","POST"],"description":"List or create/upsert a transcription. Filter list by parent_item_id and language.","scope":"productions:write"},{"path":"/api/agents/productions/[id]/transcriptions/[transcriptionId]","methods":["GET","PATCH","DELETE"],"description":"Read / partial-update / soft-delete a transcription.","scope":"productions:write"},{"path":"/api/agents/productions/[id]/clips","methods":["GET","POST"],"description":"List clips or create a draft. POST validates against ClipWriteSchema.","scope":"productions:write"},{"path":"/api/agents/productions/[id]/clips/[clipId]","methods":["GET","PATCH","DELETE"],"description":"Read / partial-update / soft-delete a clip draft.","scope":"productions:write"},{"path":"/api/agents/productions/[id]/clips/[clipId]/publish-transcription","methods":["POST"],"description":"Apply segments + corrections to source transcription, retime cues, write a new transcription. Idempotent.","scope":"productions:write"},{"path":"/api/agents/productions/[id]/clips/[clipId]/publish-video","methods":["POST"],"description":"Render a new video/audio file via the cog-clip-export model. Synchronous; pins output_item_id on the clip.","scope":"productions:write"},{"path":"/api/agents/productions/[id]/studio/projects/[projectId]","methods":["GET","PATCH","DELETE"],"description":"Read / update / delete a Studio project linked to this production.","scope":"studio:write"},{"path":"/api/agents/productions/[id]/tasks/[taskId]","methods":["GET","PATCH","DELETE"],"description":"Read / update / delete a single task.","scope":"productions:write"},{"path":"/api/agents/productions/[id]/tasks/[taskId]/notes","methods":["POST"],"description":"Append to a task notes thread (use parent task GET for the thread itself).","scope":"productions:write"},{"path":"/api/agents/productions/[id]/moodboards/[moodboardId]","methods":["GET","PATCH","DELETE"],"description":"Read / update / delete a moodboard. PATCH accepts partial body {title?, description?, canvas?}.","scope":"productions:write"},{"path":"/api/agents/productions/[id]/shotboards/[shotboardId]","methods":["GET","PATCH","DELETE"],"description":"Read / update / delete a shotboard.","scope":"productions:write"},{"path":"/api/agents/generate/image","methods":["POST"],"description":"Text-to-image generation","scope":"generate:image","idempotent":true,"returns_201_on_success":true},{"path":"/api/agents/generate/video","methods":["POST"],"description":"Text/image-to-video generation","scope":"generate:video","idempotent":true,"returns_201_on_success":true},{"path":"/api/agents/generate/video-animate","methods":["POST"],"description":"Animate still image to video","scope":"generate:video","idempotent":true,"returns_201_on_success":true},{"path":"/api/agents/generate/video-image-audio","methods":["POST"],"description":"Compose video from image + audio","scope":"generate:video","idempotent":true,"returns_201_on_success":true,"reference_schemes":["item_ref"]},{"path":"/api/agents/generate/voice","methods":["POST"],"description":"Text-to-speech","scope":"generate:audio","idempotent":true,"returns_201_on_success":true},{"path":"/api/agents/generate/voice-changer","methods":["POST"],"description":"Voice conversion","scope":"generate:audio","idempotent":true,"returns_201_on_success":true,"reference_schemes":["item_ref"]},{"path":"/api/agents/generate/music","methods":["POST"],"description":"Music generation","scope":"generate:audio","idempotent":true,"returns_201_on_success":true},{"path":"/api/agents/generate/sfx","methods":["POST"],"description":"Sound effects","scope":"generate:audio","idempotent":true,"returns_201_on_success":true,"reference_schemes":["item_ref"]},{"path":"/api/agents/generate/auto-sfx","methods":["POST"],"description":"Auto-SFX for video","scope":"generate:audio","idempotent":true,"returns_201_on_success":true,"reference_schemes":["item_ref"]},{"path":"/api/agents/generate/lipsync","methods":["POST"],"description":"Lip sync audio to a face. Send video_url (face video) for most models, or image_url (face image) for image + audio models such as minimaxH3MaxLipSync (5–14.8 s of audio; billed per second of audio at the chosen resolution: 480P/768P/1080P/2K).","scope":"generate:audio","idempotent":true,"returns_201_on_success":true,"reference_schemes":["item_ref"]},{"path":"/api/agents/generate/vision-language","methods":["POST"],"description":"Vision + language reasoning","scope":"generate:other","idempotent":true,"returns_201_on_success":true},{"path":"/api/agents/generate/character-sheet","methods":["POST"],"description":"Character reference sheet","scope":"generate:sheet","idempotent":true,"returns_201_on_success":true,"reference_schemes":["item_ref"]},{"path":"/api/agents/generate/location-sheet","methods":["POST"],"description":"Location reference sheet","scope":"generate:sheet","idempotent":true,"returns_201_on_success":true,"reference_schemes":["item_ref"]},{"path":"/api/agents/generate/prop-sheet","methods":["POST"],"description":"Prop reference sheet","scope":"generate:sheet","idempotent":true,"returns_201_on_success":true,"reference_schemes":["item_ref"]},{"path":"/api/agents/generate/set-sheet","methods":["POST"],"description":"Set reference sheet","scope":"generate:sheet","idempotent":true,"returns_201_on_success":true,"reference_schemes":["item_ref"]},{"path":"/api/agents/generate/shot-framing","methods":["POST"],"description":"Compose a shot from entities + prompt (@slug + item:<uuid>)","scope":"generate:shot-framing","idempotent":true,"returns_201_on_success":true,"reference_schemes":["item_ref","entity_slug"]},{"path":"/api/agents/generate/timeline-export","methods":["POST"],"description":"Render timeline to video","scope":"generate:other","idempotent":true,"returns_201_on_success":true},{"path":"/api/agents/generate/composition-render","methods":["POST"],"description":"Render a Hyperframes HTML composition to MP4 (HTML + assets -> MP4 via headless Chrome + FFmpeg).","scope":"generate:composition","idempotent":true,"returns_201_on_success":true,"reference_schemes":["item_ref"]},{"path":"/api/agents/compositions","methods":["GET","POST"],"description":"List (GET) or create (POST) Motion (Hyperframes) compositions. Create accepts title/description/payload (serialized HTML); defaults the production binding to the key default. Pair with composition-render to render the payload to MP4.","scope":"compositions:read | compositions:write"},{"path":"/api/agents/compositions/[compositionId]","methods":["GET","PATCH","DELETE"],"description":"Fetch (GET, full payload), update (PATCH: title/description/payload/thumbnail/status) or soft-delete (DELETE) one composition. Owner-gated → 404 when unknown/unowned.","scope":"compositions:read | compositions:write"},{"path":"/api/agents/compositions/[compositionId]/duplicate","methods":["POST"],"description":"Clone a composition (title gets \" (copy)\").","scope":"compositions:write","returns_201_on_success":true},{"path":"/api/agents/compositions/[compositionId]/revisions","methods":["GET"],"description":"Newest-first version history for one composition, without payloads (a payload can be 50KB+). Every payload change via PATCH or create appends a revision. Use with revert to run an edit/critique loop that can walk back.","scope":"compositions:read"},{"path":"/api/agents/productions/[id]/compositions","methods":["GET"],"description":"The key owner's Motion compositions bound to this production or unbound (owner/editor access), newest first, without payloads. ?limit (≤200), ?offset.","scope":"compositions:read"},{"path":"/api/agents/productions/[id]/compositions/[compositionId]","methods":["GET","PATCH"],"description":"GET: { composition, version, summary: { stage, layers[] }, html } (?include_html=false drops html; ETag = version). PATCH: { ops: [{ op, ...args }] } — the agent chat's Motion tools (update_clip, add_clip, remove_clip, reorder_clips, set_stage, set_clip_animations, replace_html, …; aliases update_layer/add_layer/remove_layer/reorder_layers/update_stage/set_html) applied atomically through the SDK parse → edit → serialize pipeline — or { payload: html } (validated, needs version). version / If-Match → 409 version_conflict when stale; 422 op_failed / invalid_payload; dry_run previews. Free (no credits). See features.composition_edit.","scope":"compositions:read | compositions:write"},{"path":"/api/agents/compositions/[compositionId]/revisions/revert","methods":["POST"],"description":"Restore a composition to an earlier revision by revision_id. Appends a revert revision rather than rewinding, so the abandoned branch stays readable and the revert is itself revertible. Owner-gated → 404 when unknown/unowned, or when the revision belongs to another composition.","scope":"compositions:write"},{"path":"/api/agents/edit/clip","methods":["POST"],"description":"Clip a video to a time range (ms or seconds). Backed by filmFunClipMedia.","scope":"edit:media","idempotent":true,"returns_201_on_success":true,"reference_schemes":["item_ref"]},{"path":"/api/agents/edit/image","methods":["POST"],"description":"Edit image (inpaint / outpaint / variation)","scope":"edit:media","idempotent":true,"returns_201_on_success":true},{"path":"/api/agents/edit/image-multiple","methods":["POST"],"description":"Batch image edits","scope":"edit:media","idempotent":true,"returns_201_on_success":true,"reference_schemes":["item_ref"]},{"path":"/api/agents/edit/image-draw","methods":["POST"],"description":"Draw on image","scope":"edit:media","idempotent":true,"returns_201_on_success":true,"reference_schemes":["item_ref"]},{"path":"/api/agents/edit/image-color-grade","methods":["POST"],"description":"Color grade","scope":"edit:media","idempotent":true,"returns_201_on_success":true,"reference_schemes":["item_ref"]},{"path":"/api/agents/edit/image-upscale","methods":["POST"],"description":"Upscale image","scope":"edit:media","idempotent":true,"returns_201_on_success":true},{"path":"/api/agents/edit/caption-burner","methods":["POST"],"description":"Burn captions onto video","scope":"edit:media","idempotent":true,"returns_201_on_success":true,"reference_schemes":["item_ref"]},{"path":"/api/agents/edit/media-merge","methods":["POST"],"description":"Concatenate videos (input_urls[], in order). parameters.outputAspectRatio (e.g. \"16:9\") or width/height set the output shape; every input is cropped to it. Omitted: the production's aspect ratio, else the first input video's own shape (the model on its own would default to 9:16).","scope":"edit:media","idempotent":true,"returns_201_on_success":true},{"path":"/api/agents/edit/video-extend","methods":["POST"],"description":"Continue a video with new footage from a prompt (MiniMax H3 Max extend, minimaxH3MaxExtend). video_url (1.6–60 s, ≤50 MB) + prompt (what happens next). duration 5–15 s of new footage (default 5), resolution 480P/768P/1080P/2K (default 768P), aspect_ratio auto|21:9|16:9|4:3|1:1|3:4|9:16, output extended (source + new, default) | continuation (new only). Billed per second of new footage at the resolution rate, plus a reference-input charge for the length of the source video (an unmeasurable source is billed as 60 s).","scope":"edit:media","idempotent":true,"returns_201_on_success":true,"reference_schemes":["item_ref"]},{"path":"/api/agents/edit/video-reframe","methods":["POST"],"description":"Reframe a video to 9:16 / 4:5 / 1:1 / 16:9. Defaults to the subject-following crop (filmFunVideoReframe): the crop follows the speaker, the audio is kept, and the item gets metadata.subject_track (track_url + summary) so captions can sit above the head. Pass model_id lumaReframeVideo to expand the frame with AI fill instead.","scope":"edit:media","idempotent":true,"returns_201_on_success":true,"reference_schemes":["item_ref"]},{"path":"/api/agents/edit/video-upscale","methods":["POST"],"description":"Upscale a video to a higher resolution. Models: topazlabsVideoUpscaler (default; target_resolution 720p|1080p|4k, target_fps 15-60), bflFluxVideoUpscale (upscale_factor 1.5-3, creativity precise|creative) or crystalVideoUpscaler (scale_factor). Billed per second of video.","scope":"edit:media","idempotent":true,"returns_201_on_success":true,"reference_schemes":["item_ref"]},{"path":"/api/agents/transcribe","methods":["POST"],"description":"Audio/video -> text (JSON audio_url OR multipart file <=4.5MB)","scope":"transcribe:write","idempotent":true,"returns_201_on_success":true,"reference_schemes":["item_ref"]},{"path":"/api/agents/tools/generator-tools","methods":["GET"],"description":"List every generator tool with its enabled models embedded (?include=models, default). Mapping endpoint that ReelKit and other agent-side registries cache to discover what models are enabled and which is the default per generator.","scope":"tools:read"},{"path":"/api/agents/tools/generator-tools/[generatorToolId]/models","methods":["GET"],"description":"List models + capabilities for a generator","scope":"tools:read"},{"path":"/api/agents/tools/generator-tools/image/models","methods":["GET"],"description":"List image-generation models + capabilities (alias for generator-tools/[id]/models with id=image)","scope":"tools:read"},{"path":"/api/agents/skill/brand-dna/extract","methods":["POST"],"description":"Extract brand DNA (palette / typography / voice / values) from a URL or pasted text.","scope":"tools:read","idempotent":true,"returns_201_on_success":true},{"path":"/api/agents/productions/[id]/publish-to-studio","methods":["POST"],"description":"Publish this production's generated video to film.fun Studio (studio.film.fun, the episodic platform) with the Studio key the agent key's owner connected in Splice (Keys & integrations). Body {project?: {studio_project_id} | {create: {title?, description?, cover_image?}} (default: the production's linked Studio project, else a new draft project), episode: {item_id, title?, description?, episode_number?, thumbnail_url?, duration_seconds?} (auto next number), beat?: {item_id, title?, description?, thumbnail_url?, duration_seconds?} (a short clip), release?: bool (default false = draft), publish_project?: bool}. Items must be completed videos. thumbnail_url (https) and duration_seconds (whole seconds >= 1) are optional overrides; otherwise the thumbnail is the item's metadata, else the generation's source image, else a merge's first input clip, else the Studio project cover, and the duration is the metadata, else the MP4's real length, else the requested duration. Idempotent: rows are matched by production + item, so a re-publish updates them and fills a thumbnail or duration the Studio row lacks (an override replaces it). Returns {project:{id,url,manage_url,status,created}, episode:{id,url,status,episode_number}, beat?, share_kit?, warnings, link}. 409 studio_not_connected / studio_disabled; 422 studio_not_ready with missing[] when the project can't be published; 409 studio_key_invalid when the stored Studio key was revoked (reconnect in Splice).","scope":"studio:write","idempotent":true},{"path":"/api/agents/productions/[id]/studio-link","methods":["GET","PUT"],"description":"The production's film.fun Studio setting: {link: {enabled, studio_project_id, studio_project_url, studio_project_title, linked_at, linked_by}}. GET needs studio:read. PUT (studio:write) {enabled?: bool, studio_project_id?: uuid | null (null unlinks), create?: {title?, description?, cover_image?}} links one of the owner's Studio projects (read with their connected Studio key), creates one, unlinks, or turns Studio off for the production.","scope":"studio:write"},{"path":"/api/agents/productions/[id]/share-links","methods":["GET","POST"],"description":"List / create share links (frame.io-style review or present). POST returns public_url.","scope":"share:write"},{"path":"/api/agents/productions/[id]/share-links/[linkId]","methods":["GET","PATCH","DELETE"],"description":"Read share link with comments + decisions (split-approval polling target). Update or delete.","scope":"share:read"},{"path":"/api/agents/video-templates","methods":["GET","POST"],"description":"List the agent client's video templates (filter by ?kind=script|storyboard|shotlist), or create one — extract from a row via {kind, name, source_id} OR raw seed via {kind, name, payload}. Source enrichment auto-snapshots the production's universe_bible into source.universe_bible when present.","scope":"productions:read","idempotent":false},{"path":"/api/agents/video-templates/[templateId]/apply","methods":["POST"],"description":"Apply a video template to a target production, creating a new scripts/storyboards/shotlists row seeded from the template's payload. Returns {applied: {kind, id}}.","scope":"productions:write","idempotent":false,"returns_201_on_success":true},{"path":"/api/agents/shotlists/[shotlistId]/promote","methods":["POST"],"description":"Promote a shotlist to a fresh shotboard. Snapshots scenes[].shots[] into the shotboard's flat shots[] (preserving order) and wires parent_shotlist_id so lineage breadcrumbs render. Body: {production_id, name?, description?, aspect_ratio?}.","scope":"productions:write","idempotent":false,"returns_201_on_success":true}],"features":{"credits":{"balance":{"route":"/api/agents/balance","method":"GET","scope":"balance:read"},"packs":{"route":"/api/agents/credits/packs","method":"GET","scope":"balance:read"},"cli":"splice wallet create | balance, splice credits packs | buy <pack> [--token USDC] [--dry-run] (https://splice.film.fun/cli/splice.mjs)","purchase":{"route":"/api/agents/credits/purchase","method":"POST","scope":"credits:write","network":"solana","tokens":["USDC","SOL","AUDD"],"packs":{"starter":{"credits":1000,"usd":10},"creator":{"credits":5000,"usd":45},"pro":{"credits":11500,"usd":99},"studio":{"credits":27000,"usd":225}},"solana_pay":{"enabled":true,"field":"payment_required.solana_pay { url, reference, status_url }","status_route":"/api/agents/credits/purchase/{transaction_id}","note":"A standard Solana Pay transfer request: any wallet can pay it, or a person can scan it as a QR code. Poll status_url until status is completed; no signature needs reporting."},"x402":{"header":"X-PAYMENT","body":"{ transaction_id, signature, payer_address }"}}},"remix":{"enabled":true,"route":"/api/agents/productions/{id}/reference-analysis","method":"POST","copies":"structure only — pacing, shot lengths, beats and framing. Never the reference’s footage, audio or artwork.","rights":{"required":true,"field":"rights: { attested: true, statement_version }","refused":{"status":400,"code":"reference_rights_required"},"recorded":"splice_reference_analyses: profile, Privy id, time, the statement and its version","likeness":"A real person’s face or voice still needs that character’s own consent record, re-checked on every generation."},"steps":["transcription (word timings)","video-analysis (shots, keyframes, contact sheets)","vision-language (the plan)"],"priced":"each step is quoted with the charge’s own pricing immediately before it runs, and reused at 0 credits when an identical read exists","spend_guards":{"dry_run":"returns the estimate and runs nothing","max_credits":{"status":409,"code":"quote_exceeds_max_credits"}},"depths":["quick","standard","deep"],"max_reference_seconds":600,"produces":"a timed plan (shots with duration, framing, dialogue and a note on what the reference does), optionally saved as a normal shotlist"},"variants":{"enabled":true,"route":"/api/agents/productions/{id}/variants","method":"POST","axes":["hook_line","cast","language","aspect","music"],"rule":"one arm changes exactly one thing; the base (\"as it is\") is priced alongside them unless include_base is false","reuse":"each arm is planned by the same planner a batch uses, so a shot the axis didn’t touch keeps its input hash and reuses the existing take at 0 credits","reach":{"hook_line":"the first shot","cast":"the shots that character appears in","language":"the shots with dialogue","aspect":"every shot","music":"no shot — it lands in the cut"},"max_arms":6,"spend_guards":{"dry_run":"returns the quote and runs nothing","max_credits":{"status":409,"code":"quote_exceeds_max_credits"}}},"studio_publish":{"enabled":true,"target":"film.fun Studio (studio.film.fun), the episodic platform. Not Splice's own video editor.","route":"/api/agents/productions/{id}/publish-to-studio","method":"POST","scope":"studio:write","link_route":"/api/agents/productions/{id}/studio-link","requires":"the agent key's owner has connected Studio in Splice (Keys & integrations); otherwise 409 studio_not_connected","kinds":{"episode":"kind episode, numbered (auto next number)","beat":"kind beat, a short clip, optional"},"idempotent":"rows are matched by production + item; a re-publish updates them. Idempotency-Key is honoured.","spends":false},"composition_edit":{"enabled":true,"routes":{"list":"GET /api/agents/productions/{id}/compositions","get":"GET /api/agents/productions/{id}/compositions/{compositionId}","edit":"PATCH /api/agents/productions/{id}/compositions/{compositionId}"},"scopes":{"read":"compositions:read","write":"compositions:write"},"summary_fields":{"stage":["id","title","width","height","duration","duration_override","background","css"],"layer":["id","kind","track","start","duration","end","text","src","class_name","style","animations","raw_html"]},"ops":["add_clip","add_clips","update_clip","move_clip","resize_clip","remove_clip","duplicate_clip","set_clip_transform","set_stage","replace_html","shift_clips","sequence_clips","reorder_clips","fit_composition_duration","set_clip_animations","add_clip_animation","remove_clip_animation","clear_clip_animations","apply_template","add_captions","reset_composition"],"op_aliases":{"add_layer":"add_clip","update_layer":"update_clip","remove_layer":"remove_clip","reorder_layers":"reorder_clips","update_stage":"set_stage","set_html":"replace_html"},"max_ops":50,"op_shape":"{ op: <name>, ...the Motion chat tool's args } — e.g. { op: \"update_clip\", clip_id, text?, style?, start?, duration?, track?, raw_html?, animations? }","atomic":true,"payload_replace":"PATCH { payload: html, version } — validated by the SDK parser (needs the stage element), stored as serialized","concurrency":{"token":"version (a content hash; also the GET ETag)","send_as":["body.version","If-Match"],"required_for":"payload","conflict":{"status":409,"code":"version_conflict","fields":["current_version","composition","summary"]}},"errors":{"op_failed":422,"invalid_payload":422,"version_required":400},"dry_run":true,"spends":false,"live_editor":"an open Motion editor polls the version and loads the change as one undoable step (or asks, when it has unsaved edits)","revisions":"each change appends a revision (source agent); revert via POST /api/agents/compositions/{id}/revisions/revert"},"quote":{"enabled":true,"route":"/api/agents/productions/{id}/quote","method":"POST","spends":false,"forms":["shot_batch: { shotlist_id, shot_ids?, model_id?, resolution?, duration_seconds?, regenerate?, reuse? }","steps: { steps: [{ key, generator, tool_id, params }] }"],"priced_by":"Forge POST /productions/{p}/quote — the same pricing function the charge uses","reuse":{"default":true,"key":"input_hash: sha256 of generator + tool_id + canonical params (upstream outputs hashed by their step hash)","line_statuses":["generate","reused","in_progress","not_needed"],"regenerate":"regenerate: [shot ids or step keys]; dependents regenerate with them"},"batch_guards":{"route":"/api/agents/productions/{id}/shot-sheet/seedance/all","dry_run":"returns { quote } and runs nothing","max_credits":{"status":409,"code":"quote_exceeds_max_credits"}}},"dialogue_voices":{"shot_field":"dialogue: [{ character_id | speaker, line }] (max 3 per shot)","character_voice":"voice_identity.voice on the client character: preset TTS voice, or a cloned sample with a consent record","reference_audio_models":["seedance2Fast","seedance25","minimaxH3"],"fallback":"other models: the line is rendered in the voice, then lip-synced (tmapppdevLipSync)","duration":"syllable estimate of the lines, rounded up to the model range, unless duration_seconds is set"},"idempotency":{"header":"Idempotency-Key","applies_to":"POST /api/agents/generate/*, /api/agents/edit/*, /api/agents/transcribe (JSON bodies)","claimed_at_request_start":true,"key_scope":"profile","max_key_length":255,"ttl_seconds":86400,"replay_header":"Idempotent-Replayed","in_progress":{"status":409,"code":"idempotency_in_progress","retry_after_seconds":5},"outcome_unknown":{"status":409,"code":"idempotency_outcome_unknown","pending_lease_seconds":330},"body_mismatch":{"status":409,"code":"conflict"},"same_key_reruns_after":"a 4xx (including 402 insufficient_credits) or a failure before Forge was called","lookup_route":"/api/agents/idempotency/{key}"},"insufficient_credits":{"status":402,"error":"insufficient_credits","fields":["message","required","balance","shortfall"]},"generate_response":{"fields":["production_item_id","job_id","production_id"]},"item_lookup":{"route":"/api/agents/productions/{id}/items/{itemId}","direct_by_id":true,"fields":["status","media_url","job_id","job_status","cancelled","error_message"]},"cancel":{"route":"/api/agents/productions/{id}/items/{itemId}/cancel","enabled":true,"refunds":true,"stops_supplier":false}}}