Genfire Public API

Full API Reference

Use Genfire as a production content engine for images, videos, speech, music, sound effects, lip-sync, product extraction, workflow automation, and webhook-driven orchestration.

Base URL

/v1

https://api.genfire.ai/v1

Auth

Bearer

API key or OAuth access token

Async Pattern

Runs

Poll GET /runs/{runId} for progress

Machine Contract

OpenAPI 3.1

Download the JSON contract

Common Conventions

  • Send Authorization: Bearer ... on every protected endpoint.
  • Send Idempotency-Key on mutating generation, workflow, batch, and product extraction endpoints that require it.
  • Most long-running operations return a run resource immediately and finish asynchronously.
  • Errors follow one JSON shape — including 429, which is problem+json like everything else (code: "rate_limited"). Example:
{
  "type": "https://api.genfire.ai/errors/invalid_prompt",
  "title": "Invalid Prompt",
  "status": 400,
  "detail": "prompt is required.",
  "code": "invalid_prompt",
  "request_id": "req_123"
}

Scope & quote error codes

  • 400 team_billing_unsupported — you passed team_id to a route that cannot bill a workspace pool yet. It refuses rather than quietly charging your personal balance; the detail says why.
  • 400 project_filing_unsupported — you passed project_id to a route whose output is not an image, video or audio a project can hold.
  • 403 insufficient_scope — team_id and project_id read the workspace / project, so the key needs teams:read / projects:read.
  • 404 invalid_team_id / 403 team_role_cannot_spend — not a member, or a role that cannot spend the pool.
  • 401 quote_invalid — the quote_token signature does not verify.
  • 409 quote_expired / 409 quote_mismatch — the quote lapsed, or the submitted inputs or price no longer match it. Both carry a FRESH quote object in the problem body, so you can resubmit in one step.
  • A run that fails on money now records the real reason — insufficient_credits, team_pool_insufficient, team_member_cap, team_monthly_cap or member_budget_exceeded — instead of provider_error.

Discovery

Public Model Aliases

Always use these aliases in API requests. Do not send internal provider model ids.

image_generation

Genfire v1

image.genfire_v1

Genfire's signature image model — tuned for ad/product creative.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4"
  ],
  "max_count": 4,
  "max_reference_images": 16
}

image_generation

Nano Banana 2

image.nano_banana_2

Google's general-purpose image model. Fast, broad subject coverage. Superseded by image.nano_banana_2_1 (same price).

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4",
    "21:9",
    "9:21"
  ],
  "max_count": 4
}

image_generation

Nano Banana 2.1

image.nano_banana_2_1 default

Google's newest Nano Banana — better visual design, mask-based editing and subject consistency than Nano Banana 2, same price. 1K/2K/4K. Default.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4",
    "21:9"
  ],
  "max_count": 4,
  "resolutions": [
    "1K",
    "2K",
    "4K"
  ]
}

image_generation

Nano Banana Pro

image.nano_banana_pro

Premium photoreal image generation, higher fidelity than Nano Banana 2.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4"
  ],
  "max_count": 4
}

image_generation

Nano Banana

image.nano_banana

Standard speed and quality. Cheaper than Pro for high-volume use.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4"
  ],
  "max_count": 4
}

image_generation

Nano Banana Lite

image.nano_banana_lite

Gemini 3.1 Flash Lite Image — ultra-low latency, cheapest tier. Supports editing.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4",
    "21:9",
    "9:21"
  ],
  "max_count": 4
}

image_generation

Nano Banana 2 Lite

image.nano_banana_2_lite

Gemini 3.1 Flash Lite Image (v2) — ultra-low latency text-to-image. No edit endpoint.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4",
    "21:9",
    "9:21"
  ],
  "max_count": 4
}

image_generation

GPT Image 2

image.gpt_image_2

OpenAI GPT Image 2 — premium photorealistic generation.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4"
  ],
  "max_count": 4,
  "max_reference_images": 16
}

image_generation

GPT Image 2.5 Sunburst

image.gpt_image_25_sunburst

OpenAI GPT Image 2.5 Sunburst — the precision tier: extra fidelity on intricate detail and tighter control across edits, at longer generation times. Same controls and price as image.gpt_image_2; use Flare when speed matters.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4"
  ],
  "max_count": 4,
  "max_reference_images": 16
}

image_generation

GPT Image 2.5 Flare

image.gpt_image_25_flare

OpenAI GPT Image 2.5 Flare — OpenAI's recommended default: higher quality than GPT Image 2 at ~50% lower latency, for everyday, social, product and high-volume work. Same controls and price as image.gpt_image_2.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4"
  ],
  "max_count": 4,
  "max_reference_images": 16
}

image_generation

Flux (trained style)

image.flux_lora

Flux with one of YOUR trained image styles (from POST /v1/image-styles or a style skill). Pass `loras: [{ id, scale? }]` with 1–3 completed image-style ids; the trigger word is added to the prompt automatically. Refuses to run without an adapter. Text-to-image only.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4"
  ],
  "max_count": 4
}

image_generation

Seedream 4.5

image.seedream_v45

ByteDance Seedream 4.5 — strong product and ad visuals, photoreal.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16"
  ],
  "max_count": 4
}

image_generation

Seedream 5.0 Lite

image.seedream_v5_lite

ByteDance Seedream 5.0 Lite — multilingual text rendering, web-search-aware.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16"
  ],
  "max_count": 4
}

image_generation

Seedream 5.0 Pro

image.seedream_v5_pro

ByteDance Seedream 5.0 Pro — flagship photoreal image + multi-reference edit, up to 2K, up to 10 reference images.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16"
  ],
  "max_count": 6
}

image_generation

Qwen Image 2

image.qwen_image_2

Alibaba Qwen Image 2 — strong typography, posters, 2K generation.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4"
  ],
  "max_count": 4
}

image_generation

Qwen Image 2 Pro

image.qwen_image_2_pro

Premium Qwen Image 2 with enhanced detail.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4"
  ],
  "max_count": 4
}

image_generation

Qwen Image 3

image.qwen_image_3

Alibaba Qwen Image 3 — bilingual (CN/EN) text rendering up to 2K. Edit mode takes 1-3 reference images, addressed as "image 1".."image 3" in the prompt.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4"
  ],
  "max_count": 4,
  "max_prompt_chars": 800,
  "max_reference_images": 3
}

image_generation

Z-Image Turbo

image.z_image_turbo

Alibaba Z-Image Turbo — 8-step distilled 6B model: photoreal output and clean EN/CN text in about a second, at the lowest price in the catalog. With `image_url` it runs image-to-image (`strength` 0-1, default 0.6 — the prompt describes the WHOLE result, not an edit instruction); add `mask_url` to inpaint only the white region.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4",
    "3:2",
    "2:3",
    "21:9"
  ],
  "max_count": 4,
  "max_reference_images": 1
}

image_generation

Z-Image Base

image.z_image_base

Alibaba Z-Image Base — the undistilled foundation model (28 steps). Slower than Turbo but with wider stylistic range, real guidance control and `negative_prompt`. Text-to-image only.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4",
    "3:2",
    "2:3",
    "21:9"
  ],
  "max_count": 4
}

image_generation

Z-Image Turbo ControlNet

image.z_image_controlnet

Z-Image Turbo guided by the STRUCTURE of a control image. `image_url` is required; `control_mode` picks what is extracted from it — `canny` (edges, default), `depth`, `pose`, or `none` when the image is already a control map. `control_scale` (0-1, default 0.75) sets how tightly the result follows it.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4",
    "3:2",
    "2:3",
    "21:9"
  ],
  "max_count": 4,
  "max_reference_images": 1
}

image_generation

Z-Image Turbo Tiling

image.z_image_tiling

Seamlessly tileable textures and patterns from Z-Image Turbo — edges wrap with no visible seam. `tiling_mode`: `both` (default), `horizontal` or `vertical`. Optional `image_url` re-tiles an existing image; add `mask_url` to repaint part of it.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4"
  ],
  "max_count": 4,
  "max_reference_images": 1
}

image_generation

Recraft V4 Vector

image.recraft_v4_vector

Recraft V4 vector output — a true SVG rather than a raster image. Logos, icons and flat illustration that has to scale cleanly.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4"
  ],
  "max_count": 4
}

image_generation

Recraft V4 Pro Vector

image.recraft_v4_pro_vector

Premium Recraft V4 vector output — true SVG at the Pro tier’s fidelity.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4"
  ],
  "max_count": 4
}

image_generation

Recraft V4.1

image.recraft_v41

Recraft V4.1 — sharper prompt control and cleaner composition than V4. Tuned for brand systems and editorial work.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4"
  ],
  "max_count": 4
}

image_generation

Recraft V4.1 Pro

image.recraft_v41_pro

Recraft V4.1 at the Pro tier — high-resolution raster for large-format and production work.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4"
  ],
  "max_count": 4
}

image_generation

Recraft V4.1 Vector

image.recraft_v41_vector

Recraft V4.1 vector output — true SVG.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4"
  ],
  "max_count": 4
}

image_generation

Recraft V4.1 Pro Vector

image.recraft_v41_pro_vector

Recraft V4.1 Pro vector output — true SVG at the Pro tier’s fidelity.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4"
  ],
  "max_count": 4
}

image_generation

Recraft V4.1 Utility

image.recraft_v41_utility

A faster, lighter V4.1 runtime for high-volume work — ideation, A/B exploration and content pipelines. Same price and parameters as image.recraft_v41; the difference is throughput.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4"
  ],
  "max_count": 4
}

image_generation

Recraft V4.1 Utility Pro

image.recraft_v41_utility_pro

V4.1 Pro output on the faster Utility runtime — large-format work at pipeline scale. Same price and parameters as image.recraft_v41_pro.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4"
  ],
  "max_count": 4
}

image_generation

Recraft V4 Styles

image.recraft_v4_style

Recraft V4 with a custom style. Supply a custom style either as `style_id` (trained earlier) or as 1-10 `image_urls` style references, which trains one inline and returns its `style_id` for reuse.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4"
  ],
  "max_count": 4,
  "max_reference_images": 10
}

image_generation

Recraft V4 Styles Pro

image.recraft_v4_style_pro

Recraft V4 Styles at the Pro tier. Supply a custom style either as `style_id` (trained earlier) or as 1-10 `image_urls` style references, which trains one inline and returns its `style_id` for reuse.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4"
  ],
  "max_count": 4,
  "max_reference_images": 10
}

image_generation

Recraft V4 Styles Vector

image.recraft_v4_style_vector

Recraft V4 Styles with true SVG output. Supply a custom style either as `style_id` (trained earlier) or as 1-10 `image_urls` style references, which trains one inline and returns its `style_id` for reuse.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4"
  ],
  "max_count": 4,
  "max_reference_images": 10
}

image_generation

Recraft V4 Styles Pro Vector

image.recraft_v4_style_pro_vector

Recraft V4 Styles, Pro tier, true SVG output. Supply a custom style either as `style_id` (trained earlier) or as 1-10 `image_urls` style references, which trains one inline and returns its `style_id` for reuse.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4"
  ],
  "max_count": 4,
  "max_reference_images": 10
}

image_generation

Recraft V4

image.recraft_v4

Design-grade image generation with reliable text rendering.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4"
  ],
  "max_count": 4
}

image_generation

Recraft V4 Pro

image.recraft_v4_pro

Premium Recraft V4 — top quality for marketing/branding visuals.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4"
  ],
  "max_count": 4
}

image_generation

Grok Imagine

image.grok_imagine

xAI Grok Imagine — stylized, strong prompt following.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16"
  ],
  "max_count": 4
}

image_generation

Grok Imagine Pro

image.grok_imagine_pro

xAI Grok Imagine Pro (Quality mode) — sharper detail, stronger text rendering, up to 2K.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4",
    "3:2",
    "2:3",
    "2:1",
    "1:2",
    "20:9",
    "9:20",
    "19.5:9",
    "9:19.5"
  ],
  "max_count": 4,
  "resolutions": [
    "1K",
    "2K"
  ]
}

image_generation

Grok Imagine 2.0

image.grok_imagine_2

xAI Grok Imagine 2.0 — the latest Grok Imagine generation. Up to 2K, 13 aspect ratios, and a quality tier (low | medium, default medium). Edit accepts up to 3 reference images.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4",
    "3:2",
    "2:3",
    "2:1",
    "1:2",
    "20:9",
    "9:20",
    "19.5:9",
    "9:19.5"
  ],
  "max_count": 4,
  "resolutions": [
    "1K",
    "2K"
  ],
  "qualities": [
    "low",
    "medium"
  ],
  "max_input_images": 3
}

image_generation

Muse Image

image.muse_image

Meta Muse Image — faithful instruction-following with accurate fine detail (text, plots, QR codes). Edit mode does precise, localized changes and composes from up to 10 reference images. No resolution or quality control; omitting aspect_ratio lets Muse size the output from the prompt.

{
  "aspect_ratios": [
    "21:9",
    "16:9",
    "4:3",
    "3:2",
    "1:1",
    "2:3",
    "3:4",
    "9:16",
    "9:21"
  ],
  "max_count": 4,
  "max_reference_images": 10
}

image_generation

Ideogram 4.5

image.ideogram_v4_5

Ideogram 4.5 — posters, logos and designs with accurate typography: quote the exact words and mention the text early in the prompt. Quality low | medium | high (default medium), each its own price; 1K | 2K (default 2K), same price. Edit reads the first image as the source plus up to 4 references (3 with a mask), takes mask_url, and edit_precision: "high" (Precise Edit) restores the pixels the edit did not touch, at no extra cost.

{
  "aspect_ratios": [
    "1:1",
    "16:9",
    "9:16",
    "4:3",
    "3:4",
    "3:2",
    "2:3",
    "5:4",
    "4:5",
    "16:10",
    "10:16",
    "2:1",
    "1:2",
    "3:1",
    "1:3"
  ],
  "max_count": 4,
  "resolutions": [
    "1K",
    "2K"
  ],
  "qualities": [
    "low",
    "medium",
    "high"
  ],
  "max_reference_images": 5,
  "edit_precisions": [
    "regular",
    "high"
  ]
}

video_generation

Flux 3

video.flux_3

Black Forest Labs Flux 3 — frontier video with synchronized audio included at no extra cost. Routes to text/image-to-video, first-last-frame, keyframe pinning (keyframes[]), or extend (source_video_url). 5-20s, 720p or 1080p.

{
  "aspect_ratios": [
    "auto",
    "21:9",
    "2:1",
    "16:9",
    "4:3",
    "1:1",
    "3:4",
    "9:16"
  ],
  "duration_seconds": [
    5,
    6,
    7,
    8,
    9,
    10,
    11,
    12,
    13,
    14,
    15,
    16,
    17,
    18,
    19,
    20
  ],
  "resolutions": [
    "720p",
    "1080p"
  ],
  "supports_audio": true,
  "max_keyframes": 10,
  "source_video_max_seconds": 15,
  "source_video_max_mb": 50
}

video_generation

Flux 3 Draft

video.flux_3_draft

Cheap 720p Flux 3 preview (~3.5x less than full quality). Returns a draft_cache_url alongside the video; pass that back to this model to render the same shot at full 1080p. Same four routing modes as Flux 3.

{
  "aspect_ratios": [
    "auto",
    "21:9",
    "2:1",
    "16:9",
    "4:3",
    "1:1",
    "3:4",
    "9:16"
  ],
  "duration_seconds": [
    5,
    6,
    7,
    8,
    9,
    10,
    11,
    12,
    13,
    14,
    15,
    16,
    17,
    18,
    19,
    20
  ],
  "resolutions": [
    "720p"
  ],
  "supports_audio": true,
  "max_keyframes": 10,
  "source_video_max_seconds": 15,
  "source_video_max_mb": 50
}

video_generation

Flux 3 Edit

video.flux_3_edit

Flux 3 video edit — re-renders a source clip from a text prompt while preserving its motion, timing and framing (restyle, regrade, change the weather, wardrobe or set). Requires source_video_url (MP4, under 15s and 50 MB); output is 720p at the clip's own length. No duration, aspect-ratio or resolution controls. Far cheaper per second than a fresh Flux 3 render.

{
  "source_video_max_seconds": 15,
  "source_video_max_mb": 50,
  "resolutions": [
    "720p"
  ]
}

video_generation

Veo 3.1

video.veo_3_1 default

Google Veo 3.1 — premium text/image-to-video with native audio. Supports first-last-frame interpolation. Default.

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    4,
    5,
    8
  ],
  "supports_audio": true
}

video_generation

Veo 3.1 Fast

video.veo_3_1_fast

Cheaper, faster Veo 3.1 variant. Supports first-last-frame interpolation.

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    4,
    5,
    8
  ],
  "supports_audio": true
}

video_generation

Veo 3.1 Lite

video.veo_3_1_lite

Lower-cost Lite variant of Veo 3.1. Supports first-last-frame interpolation.

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    4,
    5,
    8
  ],
  "supports_audio": true
}

video_generation

Veo 3.1 Extend

video.veo_3_1_extend

Extend an existing Veo-created video by up to 7 seconds. Requires source_video_url.

{
  "aspect_ratios": [
    "16:9",
    "9:16"
  ],
  "duration_seconds": [
    7
  ],
  "supports_audio": true
}

video_generation

Veo 3

video.veo_3

Veo 3 — high-quality cinematic generation with audio (predecessor to Veo 3.1).

{
  "aspect_ratios": [
    "16:9",
    "9:16"
  ],
  "duration_seconds": [
    8
  ],
  "supports_audio": true
}

video_generation

Gemini Omni Flash 1.1

video.gemini_omni_flash_v11

Google Gemini Omni Flash 1.1 — adds a 360p-4k resolution ladder, an end-frame on image-to-video, and reference video clips. 3-10s, 16:9 or 9:16. Control pacing and audio in the prompt. Always generates its own audio (speech, ambience, music) — direct it in the prompt; `supports_audio: false` in limits means only that there is no separate audio on/off toggle, not that output is silent.

{
  "aspect_ratios": [
    "16:9",
    "9:16"
  ],
  "duration_seconds": [
    3,
    4,
    5,
    6,
    7,
    8,
    9,
    10
  ],
  "resolutions": [
    "360p",
    "720p",
    "1080p",
    "4k"
  ],
  "reference_image_urls": 10,
  "reference_video_urls": 3,
  "supports_audio": false
}

video_generation

Gemini Omni Flash

video.gemini_omni_flash

Google Gemini Omni Flash — fast, low-cost text/image-to-video. Supports reference images and video-to-video editing. 3-10s, 16:9 or 9:16. Control pacing and audio in the prompt. Always generates its own audio (speech, ambience, music) — direct it in the prompt; `supports_audio: false` in limits means only that there is no separate audio on/off toggle, not that output is silent.

{
  "aspect_ratios": [
    "16:9",
    "9:16"
  ],
  "duration_seconds": [
    3,
    4,
    5,
    6,
    7,
    8,
    9,
    10
  ],
  "supports_audio": false
}

video_generation

Veo 3 Fast

video.veo_3_fast

Cheaper Veo 3 variant — same quality tier, faster turnaround.

{
  "aspect_ratios": [
    "16:9",
    "9:16"
  ],
  "duration_seconds": [
    8
  ],
  "supports_audio": true
}

video_generation

Kling O3 Standard

video.kling_o3

Kling O3 standard — smooth motion, strong subject consistency.

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    5,
    10
  ],
  "supports_audio": true
}

video_generation

Kling O3 Pro

video.kling_o3_pro

Pro tier of Kling O3 — better quality at higher cost. Supports video-to-video reference (use source_video_url + reference_image_urls).

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    5,
    10
  ],
  "supports_audio": true
}

video_generation

Kling O3 4K

video.kling_o3_4k

Kling O3 at native 4K. Higher cost, top-tier quality. Also video-to-video at 4K: `source_video_url` + `reference_image_urls` restyles the clip while binding the references (3-15s, `duration` chosen by you); `source_video_url` ALONE is a source-preserving edit — same length and framing as the source (.mp4/.mov, 3-15s, 720-3840px, ≤200MB), billed per second of the SOURCE clip.

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    5,
    10
  ],
  "resolution": "4k",
  "supports_audio": true
}

video_generation

Kling V3 Standard

video.kling_v3

Kling V3 standard — text or image to video, 3-15s, with generated audio, an end frame on image-to-video, and multi-shot prompts. Use video.kling_v3_turbo for the faster, cheaper tier and video.kling_o3 for reference-to-video.

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    3,
    4,
    5,
    6,
    7,
    8,
    9,
    10,
    11,
    12,
    13,
    14,
    15
  ],
  "supports_audio": true
}

video_generation

Kling V3 Pro

video.kling_v3_pro

Kling V3 pro — the higher-quality V3 tier. Text or image to video, 3-15s, generated audio, end frame on image-to-video, multi-shot prompts.

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    3,
    4,
    5,
    6,
    7,
    8,
    9,
    10,
    11,
    12,
    13,
    14,
    15
  ],
  "supports_audio": true
}

video_generation

Kling V3 Turbo Standard

video.kling_v3_turbo

Kling 3.0 Turbo standard (720p) — the fast, cheaper Kling V3 tier. Text or image to video, 3–15s, multi-shot prompts. No end frame, no elements, no audio.

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    3,
    4,
    5,
    6,
    7,
    8,
    9,
    10,
    11,
    12,
    13,
    14,
    15
  ],
  "supports_audio": false
}

video_generation

Kling V3 Turbo Pro

video.kling_v3_turbo_pro

Kling 3.0 Turbo pro (1080p) — fast Kling V3 at the standard-tier price. Text or image to video, 3–15s, multi-shot prompts. No end frame, no elements, no audio.

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    3,
    4,
    5,
    6,
    7,
    8,
    9,
    10,
    11,
    12,
    13,
    14,
    15
  ],
  "supports_audio": false
}

video_generation

Kling V3 Motion Control

video.kling_v3_motion_control

Transfer motion from a reference video onto a character image. Requires both image_url (character) and source_video_url (motion reference).

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    5
  ]
}

video_generation

Kling V3 Pro Motion Control

video.kling_v3_pro_motion_control

Pro-tier Kling V3 motion-control — higher quality character animation. Requires both image_url and source_video_url.

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    5
  ]
}

video_generation

Kling V3 4K

video.kling_v3_4k

Kling V3 at 4K resolution — the delivery tier. Text or image to video, 3-15s, generated audio, end frame on image-to-video.

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    3,
    4,
    5,
    6,
    7,
    8,
    9,
    10,
    11,
    12,
    13,
    14,
    15
  ],
  "resolution": "4k",
  "supports_audio": true
}

video_generation

Kling 2.6

video.kling_v26

Kling 2.6 — text/image to video. Kling 2.6 has a single generation tier; this alias resolves to the same endpoints as video.kling_v26_pro.

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    5,
    10
  ]
}

video_generation

Kling 2.6 Pro

video.kling_v26_pro

Kling 2.6 pro tier — higher quality.

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    5,
    10
  ]
}

video_generation

Kling 2.6 Motion Control

video.kling_v26_motion_control

Motion-control variant of Kling 2.6 (legacy). Requires image_url + source_video_url.

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    5
  ]
}

video_generation

Kling 2.5 Turbo Pro

video.kling_v25_turbo

Faster Kling variant (legacy but reliable).

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    5,
    10
  ]
}

video_generation

Kling 2.1 Master

video.kling_v21_master

Kling 2.1 Master (legacy) — enhanced quality.

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    5,
    10
  ]
}

video_generation

H3 Max Multi Angle

video.hailuo_03_max_multi_angle

H3 Max image-to-video on an explicit camera path: hand it ONE image and a `camera_path` preset (orbit, half_orbit, dolly_in, crane_up, top_down, spiral, hero_reveal, …) or 2-12 `camera_trajectory` keyframes of azimuth / elevation / distance, and it renders 5-15s of that move with the scene held rigid — product turnarounds, parallax reveals, virtual dolly shots. 480p/768p/1080p at the H3 Max rate (1080p bills 2x the 768p rate). Image-to-video ONLY: no text-to-video, no end frame, no references.

{
  "duration_seconds": [
    5,
    6,
    7,
    8,
    9,
    10,
    11,
    12,
    13,
    14,
    15
  ],
  "resolutions": [
    "480p",
    "768p",
    "1080p"
  ],
  "camera_paths": [
    "orbit",
    "orbit_reverse",
    "half_orbit",
    "quarter_orbit",
    "dolly_in",
    "dolly_out",
    "crane_up",
    "low_angle",
    "top_down",
    "arc_push",
    "spiral",
    "hero_reveal"
  ],
  "camera_keyframes": {
    "min": 2,
    "max": 12
  }
}

video_generation

H3 Max Styles

video.hailuo_03_max_styles

H3 Max with a baked-in look: pick ONE `video_style` — `vhs` (VHS), `retro_toon_70s` (Retro Toon 70s), `low_poly` (Low Poly), `hand_drawn` (Hand Drawn), `16bit_pixel` (16-bit Pixel) — and it renders your prompt in that style, 5-15s at a fixed 768p with audio, one flat per-second rate for every style (resolution does not scale it). Text-to-video, or image-to-video from an optional `image_url` first frame (its ratio then sets the canvas and `aspect_ratio` is ignored). `video_style` is REQUIRED; `damage_level` (light / medium / heavy, default medium) is the VHS tape wear and is accepted on `vhs` only. A prompt is required — the style is applied to the scene you describe. No end frame, no references, no trained models, no resolution choice.

{
  "aspect_ratios": [
    "21:9",
    "16:9",
    "4:3",
    "1:1",
    "3:4",
    "9:16"
  ],
  "duration_seconds": [
    5,
    6,
    7,
    8,
    9,
    10,
    11,
    12,
    13,
    14,
    15
  ],
  "resolutions": [
    "768p"
  ],
  "video_styles": [
    "vhs",
    "retro_toon_70s",
    "low_poly",
    "hand_drawn",
    "16bit_pixel"
  ],
  "damage_levels": [
    "light",
    "medium",
    "heavy"
  ],
  "damage_level_styles": [
    "vhs"
  ],
  "prompt_max_length": 49800
}

video_generation

H3 Max Extend

video.hailuo_03_max_extend

H3 Max Extend — continues an existing clip from a prompt. Pass `source_video_url` (1.625-60s, ≤50 MB) and a prompt describing what happens next; `duration` (5-15s) is the length of the NEW footage. `extend_output`: `extended` (default — the source plus the new footage as one clip) or `continuation` (the new footage only). `aspect_ratio` defaults to `auto` (follow the source). 480p/768p/1080p/2k on the H3 Max per-second ladder (1080p 2x, 2k 4x the 768p rate) plus a small source-clip token surcharge. No first frame, references or text-only mode.

{
  "aspect_ratios": [
    "auto",
    "21:9",
    "16:9",
    "4:3",
    "1:1",
    "3:4",
    "9:16"
  ],
  "duration_seconds": [
    5,
    6,
    7,
    8,
    9,
    10,
    11,
    12,
    13,
    14,
    15
  ],
  "resolutions": [
    "480p",
    "768p",
    "1080p",
    "2k"
  ],
  "extend_outputs": [
    "extended",
    "continuation"
  ],
  "source_video_min_seconds": 1.625,
  "source_video_max_seconds": 60,
  "source_video_max_mb": 50,
  "prompt_max_length": 50000
}

video_generation

H3 Max 3D to Video

video.hailuo_03_max_3d

H3 Max 3D to Video — turns a 3D render (Blender, previs, greybox) into a photoreal clip that keeps its camera, timing, trajectories and object count. Pass `source_video_url` (≤15s, strict); the output matches its length and framing, so there is NO `duration` or `aspect_ratio` control. `prompt` is OPTIONAL scene intent (≤2000 chars). Optional `reference_image_urls` (≤8) set the look of every shot; without them the model generates up to `max_generated_reference_images` (1-8, default 2) appearance references itself. 480p/768p/1080p. Billed per request: a flat planning fee + the H3 Max per-second rate on the source length (5s minimum) + each generated reference + a source-clip token surcharge — call POST /v1/models/estimate-cost with the source_video_url for the exact number.

{
  "resolutions": [
    "480p",
    "768p",
    "1080p"
  ],
  "max_reference_images": 8,
  "max_generated_reference_images": {
    "min": 1,
    "max": 8,
    "default": 2
  },
  "source_video_max_seconds": 15,
  "prompt_max_length": 2000
}

video_generation

H3 Max Insert

video.hailuo_03_max_insert

H3 Max Insert — cuts a NEW scene into an existing clip, then hands back to the original footage. Pass `source_video_url` (≤60s, ≤50 MB) plus `insert_start_time` (where the new scene begins, ≥1.625s) and `insert_resume_time` (where the original picks up again: later than the start, within the first 60s and before the clip ends — leave some footage after it for the transition), both REQUIRED, in seconds into `source_video_url` as supplied (the source is never trimmed first). Guide the new scene with `prompt` and/or up to 9 `reference_image_urls` and 3 `reference_video_urls` (15s combined) — at least one is required. Returns the WHOLE edited clip: the lead-in up to the start, the new scene, then the source from the resume point on. `duration` (5-13s) is the length of the NEW scene only — the only seconds billed — whatever span it replaces; reference clips add a small token surcharge on their measured (trimmed) length. `color_match` (default true) grades the new scene to the source. 480p or 768p (480p bills 5/6 of the 768p rate). No aspect_ratio (the output follows the source), no audio toggle, no first frame, no reference audio.

{
  "duration_seconds": [
    5,
    6,
    7,
    8,
    9,
    10,
    11,
    12,
    13
  ],
  "resolutions": [
    "480p",
    "768p"
  ],
  "max_reference_images": 9,
  "max_reference_videos": 3,
  "max_reference_audio": 0,
  "max_reference_video_seconds": 15,
  "max_reference_video_seconds_total": 15,
  "insert_min_start_seconds": 1.625,
  "insert_max_time_seconds": 60,
  "source_video_max_seconds": 60,
  "source_video_max_mb": 50,
  "prompt_max_length": 50000
}

video_generation

H3 Max Recast

video.hailuo_03_max_recast

H3 Max Recast — recasts the people in a video from up to 4 reference photos, keeping the motion, camera, cuts and sound. Pass `source_video_url` (5-30s, strict; no single shot longer than 15s) and `reference_image_urls` (1-4, one photo per new person — photo 1 replaces the leftmost main person, photo 2 the next, and so on), both REQUIRED. `prompt` is OPTIONAL (≤2000 chars): say who becomes whom, or what else to keep or change. The output keeps the source's length and framing, so there is NO `duration` or `aspect_ratio`. 768p or 1080p (default 1080p; 768p bills 2/3 of the 1080p rate). Billed per second of the SOURCE as measured server-side (rounded up, 5s minimum); the photos are free, and a source that cannot be measured is refused rather than guessed — call POST /v1/models/estimate-cost with the source_video_url for the exact number. No reference clips or audio.

{
  "resolutions": [
    "768p",
    "1080p"
  ],
  "min_reference_images": 1,
  "max_reference_images": 4,
  "source_video_min_seconds": 5,
  "source_video_max_seconds": 30,
  "source_video_max_shot_seconds": 15,
  "prompt_max_length": 2000
}

video_generation

H3 Max Turbo

video.hailuo_03_max_turbo

The Turbo tier of H3 Max — the same 480p/768p/1080p output from text or a first frame (with optional last-frame keyframe), at half the H3 Max price per second (1080p bills 2x the 768p rate). Text-to-video and image-to-video only: there is no reference mode, so use video.hailuo_03_max for references and video.hailuo_03 for 2K.

{
  "aspect_ratios": [
    "21:9",
    "16:9",
    "4:3",
    "1:1",
    "3:4",
    "9:16"
  ],
  "duration_seconds": [
    5,
    6,
    7,
    8,
    9,
    10,
    11,
    12,
    13,
    14,
    15
  ],
  "resolutions": [
    "480p",
    "768p",
    "1080p"
  ]
}

video_generation

H3 Max

video.hailuo_03_max

A post-trained MiniMax H3 variant — stronger prompt adherence and higher throughput, from text, a first frame (with optional last-frame keyframe), or reference images/videos/audio. Renders 480p/768p/1080p (1080p bills 2x the 768p rate); use video.hailuo_03 for 2K.

{
  "aspect_ratios": [
    "21:9",
    "16:9",
    "4:3",
    "1:1",
    "3:4",
    "9:16"
  ],
  "duration_seconds": [
    5,
    6,
    7,
    8,
    9,
    10,
    11,
    12,
    13,
    14,
    15
  ],
  "resolutions": [
    "480p",
    "768p",
    "1080p"
  ],
  "reference_image_urls": 9,
  "reference_video_urls": 3,
  "reference_audio_urls": 3,
  "reference_media_seconds": [
    2,
    15
  ]
}

video_generation

Hailuo 03

video.hailuo_03

MiniMax Hailuo 03 — 2K video from text, a first frame (with optional last-frame keyframe), or reference images/videos/audio. The only model that runs your TRAINED MODELS: pass `loras: [{ id, scale? }]` (up to 3, from POST /v1/loras) and the clip renders through the matching Hailuo 03 LoRA endpoint.

{
  "aspect_ratios": [
    "21:9",
    "16:9",
    "4:3",
    "1:1",
    "3:4",
    "9:16"
  ],
  "duration_seconds": [
    5,
    6,
    7,
    8,
    9,
    10,
    11,
    12,
    13,
    14,
    15
  ],
  "resolutions": [
    "768p",
    "2k"
  ],
  "reference_image_urls": 9,
  "reference_video_urls": 3,
  "reference_audio_urls": 3,
  "reference_media_seconds": [
    2,
    15
  ],
  "loras": 3
}

video_generation

Hailuo 2.3

video.hailuo_23

MiniMax Hailuo 2.3 — dynamic video generation.

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    6,
    10
  ]
}

video_generation

Hailuo 2.3 Pro

video.hailuo_23_pro

MiniMax Hailuo 2.3 Pro tier.

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    6,
    10
  ]
}

video_generation

Hailuo 02

video.hailuo_02

MiniMax Hailuo 02 (legacy) — image-to-video standard tier.

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    6,
    10
  ]
}

video_generation

Hailuo 02 Fast

video.hailuo_02_fast

Faster Hailuo 02 image-to-video variant.

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    6,
    10
  ]
}

video_generation

Hailuo 2.3 Fast

video.hailuo_23_fast

Faster Hailuo 2.3 image-to-video standard tier.

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    6,
    10
  ]
}

video_generation

Hailuo 2.3 Fast Pro

video.hailuo_23_fast_pro

Faster Hailuo 2.3 pro-tier image-to-video.

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    6,
    10
  ]
}

video_generation

Luma Dream Machine

video.luma_dream_machine

Luma Dream Machine — fluid motion, distinctive style.

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    5,
    9
  ]
}

video_generation

WAN 2.2

video.wan_v22

WAN 2.2 — strong character animation.

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    5,
    8
  ]
}

video_generation

WAN 2.5 Preview

video.wan_25_preview

WAN 2.5 preview — newer than 2.2, may change without notice.

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    5,
    8
  ]
}

video_generation

WAN Animate (Replace)

video.wan_animate_replace

WAN animate-replace — swap subject in source video using a reference image. Requires image_url + source_video_url.

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    5
  ]
}

video_generation

WAN Animate (Move)

video.wan_animate_move

WAN animate-move — animate static images with natural motion from a reference video. Requires image_url + source_video_url.

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    5
  ]
}

video_generation

Seedance 2.0

video.seedance_2_0

ByteDance Seedance 2.0 — text/image/reference to video. Supports up to 4k.

{
  "aspect_ratios": [
    "auto",
    "21:9",
    "16:9",
    "4:3",
    "1:1",
    "3:4",
    "9:16"
  ],
  "duration_seconds": [
    4,
    5,
    6,
    7,
    8,
    9,
    10,
    11,
    12,
    13,
    14,
    15
  ],
  "resolutions": [
    "480p",
    "720p",
    "1080p",
    "4k"
  ],
  "supports_audio": true
}

video_generation

Seedance 2.0 Fast

video.seedance_2_0_fast

Faster, cheaper Seedance 2.0 variant. Caps at 720p.

{
  "aspect_ratios": [
    "auto",
    "21:9",
    "16:9",
    "4:3",
    "1:1",
    "3:4",
    "9:16"
  ],
  "duration_seconds": [
    4,
    5,
    6,
    7,
    8,
    9,
    10,
    11,
    12,
    13,
    14,
    15
  ],
  "resolutions": [
    "480p",
    "720p"
  ],
  "supports_audio": true
}

video_generation

Seedance 2.0 Mini

video.seedance_2_0_mini

Smallest, cheapest Seedance 2.0 variant. Caps at 720p; no bitrate_mode.

{
  "aspect_ratios": [
    "auto",
    "21:9",
    "16:9",
    "4:3",
    "1:1",
    "3:4",
    "9:16"
  ],
  "duration_seconds": [
    4,
    5,
    6,
    7,
    8,
    9,
    10,
    11,
    12,
    13,
    14,
    15
  ],
  "resolutions": [
    "480p",
    "720p"
  ],
  "supports_audio": true
}

video_generation

Seedance 2.5

video.seedance_2_5

ByteDance Seedance 2.5 — text/image/reference to video, and the engine behind Genfire Genjudo. 4-30s clips with native audio; reference-to-video takes up to 30 images, 10 videos, and 10 audio clips and a `task` of reference (motion transfer), editing (video edit) or extension. 480p/720p/1080p (no 4k) and no speed tiers. `draft: true` renders a 480p preview at the 480p price that POST /v1/videos/generations/{id}/final turns into the native 1080p final of the same shot within 7 days.

{
  "aspect_ratios": [
    "auto",
    "21:9",
    "16:9",
    "4:3",
    "1:1",
    "3:4",
    "9:16"
  ],
  "duration_seconds": [
    4,
    5,
    6,
    7,
    8,
    9,
    10,
    11,
    12,
    13,
    14,
    15,
    16,
    17,
    18,
    19,
    20,
    21,
    22,
    23,
    24,
    25,
    26,
    27,
    28,
    29,
    30
  ],
  "resolutions": [
    "480p",
    "720p",
    "1080p"
  ],
  "supports_audio": true,
  "max_reference_images": 30,
  "max_reference_videos": 10,
  "max_reference_audio": 10,
  "min_reference_video_seconds": 2,
  "max_reference_video_seconds": 30,
  "max_reference_video_seconds_total": 30,
  "video_tasks": [
    "reference",
    "editing",
    "extension"
  ]
}

video_generation

Wan 3.0

video.wan_3

Alibaba Wan 3.0 — text/image/reference to video. 2-30s clips with native synchronized audio; reference-to-video takes up to 10 images, 5 videos, and 5 audio clips. 480p/720p/1080p (default 1080p, no 4k) and no speed tiers.

{
  "aspect_ratios": [
    "adaptive",
    "16:9",
    "4:3",
    "1:1",
    "3:4",
    "9:16"
  ],
  "duration_seconds": [
    2,
    3,
    4,
    5,
    6,
    7,
    8,
    9,
    10,
    11,
    12,
    13,
    14,
    15,
    16,
    17,
    18,
    19,
    20,
    21,
    22,
    23,
    24,
    25,
    26,
    27,
    28,
    29,
    30
  ],
  "resolutions": [
    "480p",
    "720p",
    "1080p"
  ],
  "supports_audio": true,
  "max_reference_images": 10,
  "max_reference_videos": 5,
  "max_reference_audio": 5
}

video_generation

Wan 3.0 Prime

video.wan_3_prime

Alibaba Wan 3.0 Prime — the faster, higher-fidelity Wan 3.0 tier at 1.4x the price. Identical inputs: text/image/reference to video, 2-30s, native audio, up to 10 image + 5 video + 5 audio references, 480p/720p/1080p.

{
  "aspect_ratios": [
    "adaptive",
    "16:9",
    "4:3",
    "1:1",
    "3:4",
    "9:16"
  ],
  "duration_seconds": [
    2,
    3,
    4,
    5,
    6,
    7,
    8,
    9,
    10,
    11,
    12,
    13,
    14,
    15,
    16,
    17,
    18,
    19,
    20,
    21,
    22,
    23,
    24,
    25,
    26,
    27,
    28,
    29,
    30
  ],
  "resolutions": [
    "480p",
    "720p",
    "1080p"
  ],
  "supports_audio": true,
  "max_reference_images": 10,
  "max_reference_videos": 5,
  "max_reference_audio": 5
}

video_generation

Grok Imagine Video

video.grok_video

xAI Grok Imagine — high-fidelity text/image to video.

{
  "aspect_ratios": [
    "16:9",
    "4:3",
    "3:2",
    "1:1",
    "2:3",
    "3:4",
    "9:16"
  ],
  "duration_seconds": [
    1,
    2,
    3,
    4,
    5,
    6,
    7,
    8,
    9,
    10
  ],
  "resolutions": [
    "480p",
    "720p"
  ]
}

video_generation

Grok Imagine Video v1.5

video.grok_video_v15

xAI Grok Imagine v1.5 — text-to-video, image-to-video, or reference-to-video from up to 7 reference images.

{
  "duration_seconds": [
    1,
    2,
    3,
    4,
    5,
    6,
    7,
    8,
    9,
    10,
    11,
    12,
    13,
    14,
    15
  ],
  "resolutions": [
    "480p",
    "720p",
    "1080p"
  ],
  "aspect_ratios": [
    "16:9",
    "4:3",
    "3:2",
    "1:1",
    "2:3",
    "3:4",
    "9:16"
  ]
}

video_generation

Grok Imagine v1.5 Lite

video.grok_video_v15_lite

xAI Grok Imagine v1.5 Lite — the budget Grok v1.5: text-to-video or image-to-video, any whole second 1-15 (default 5), 480p / 720p / 1080p (default 720p). No reference images. aspect_ratio applies to text-to-video only; image-to-video follows the start image. 1080p bills 7x the 480p rate.

{
  "duration_seconds": [
    1,
    2,
    3,
    4,
    5,
    6,
    7,
    8,
    9,
    10,
    11,
    12,
    13,
    14,
    15
  ],
  "resolutions": [
    "480p",
    "720p",
    "1080p"
  ],
  "aspect_ratios": [
    "16:9",
    "4:3",
    "3:2",
    "1:1",
    "2:3",
    "3:4",
    "9:16"
  ],
  "max_prompt_length": 4096
}

video_generation

Grok Imagine Edit

video.grok_video_edit

Edit existing videos with text prompts. Requires source_video_url.

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    4,
    5,
    8,
    10
  ]
}

video_generation

Grok Imagine Extend

video.grok_video_extend

Extend an existing video by 2–10s. Requires source_video_url.

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "duration_seconds": [
    2,
    5,
    10
  ]
}

video_generation

Happy Horse v1.1

video.happy_horse

Alibaba Happy Horse v1.1 — text/image/reference-to-video, plus video-edit.

{
  "aspect_ratios": [
    "16:9",
    "9:16",
    "1:1",
    "4:3",
    "3:4",
    "21:9",
    "9:21",
    "5:4",
    "4:5"
  ],
  "duration_seconds": [
    3,
    4,
    5,
    6,
    7,
    8,
    9,
    10,
    11,
    12,
    13,
    14,
    15
  ]
}

speech_generation

ElevenLabs Turbo v2.5

speech.elevenlabs_turbo_v2_5

Legacy low-latency TTS — deprecated upstream in favour of Flash v2.5 (same price, lower latency). Still works when pinned.

{
  "max_characters": 40000
}

speech_generation

ElevenLabs Flash v2.5

speech.elevenlabs_flash_v2_5

Fastest ElevenLabs TTS (~75ms), lowest cost, 32 languages, up to 40,000 characters. Accepts speed, language_code, seed, previous_text/next_text and apply_text_normalization. A request that names no model and is longer than the default (speech.elevenlabs_v4_turbo) accepts runs here.

{
  "max_characters": 40000
}

speech_generation

ElevenLabs Text to Dialogue (v3)

speech.elevenlabs_dialogue_v3

Previous-generation Text to Dialogue on Eleven v3 (speech.elevenlabs_dialogue_v4 is the default). Multi-speaker conversation rendered as ONE audio file with matched prosody between lines. Send `dialogue: [{ text, voice_id }]` (up to 10 distinct ElevenLabs voices, 2000 characters total) instead of text + voice_id; lines accept v3 audio tags such as [whispers] or [laughs]. Optional stability (0 | 0.5 | 1), seed, language_code, with_timestamps (returns per-word timings + per-line voice segments). Billed like TTS on total characters.

{
  "max_characters": 2000,
  "max_voices": 10
}

speech_generation

ElevenLabs Text to Dialogue (v4)

speech.elevenlabs_dialogue_v4

Multi-speaker conversation on Eleven v4 (ElevenLabs' recommended dialogue model) — the default dialogue model: `dialogue` sent with no model, or with any alias that is not dialogue-capable, runs here. Rendered as ONE audio file with matched prosody between lines. Send `dialogue: [{ text, voice_id }]` (up to 10 distinct ElevenLabs voices, 2000 characters total) instead of text + voice_id; lines accept audio tags such as [whispers] or [laughs]. Optional stability (any value 0–1), seed, language_code, with_timestamps (returns per-word timings + per-line voice segments). Billed like TTS on total characters.

{
  "max_characters": 2000,
  "max_voices": 10
}

speech_generation

ElevenLabs v4

speech.elevenlabs_v4

Most expressive ElevenLabs voice model: audio tags such as [whispers] or [laughs], 85 languages, up to 10,000 characters per request. Voice settings are stability + similarity only — style, speed and speaker boost are ignored. Accepts language_code and seed.

{
  "max_characters": 10000
}

speech_generation

ElevenLabs v4 Turbo

speech.elevenlabs_v4_turbo default

Default speech model. Eleven v4 expressiveness (audio tags, 85 languages) at ~100 ms median inference latency and half v4's cost; up to 10,000 characters per request (a request that names no model and is longer runs on speech.elevenlabs_flash_v2_5). Voice settings are stability + similarity only — style, speed and speaker boost are ignored; sending `speed` without a model uses speech.elevenlabs_flash_v2_5 automatically, as does a no-model request on one of your account's cloned ElevenLabs voices. `dialogue` requests sent with this alias run on Eleven v4 dialogue (speech.elevenlabs_dialogue_v4).

{
  "max_characters": 10000
}

speech_generation

ElevenLabs v3

speech.elevenlabs_v3

Previous-generation expressive voice model (audio tags such as [whispers]); up to 5,000 characters. Superseded by speech.elevenlabs_v4.

{
  "max_characters": 5000
}

speech_generation

ElevenLabs Multilingual v2

speech.elevenlabs_multilingual_v2

29 languages, high fidelity.

{
  "max_characters": 10000
}

speech_generation

Seed Audio 1.0

speech.seed_audio_1_0

ByteDance Seed Audio 1.0 — general text-to-audio: natural multilingual TTS plus full audio scenes (dialogue, ambience, radio dramas) from a prompt. voice_id is OPTIONAL and takes a Seed preset name (e.g. `vivi_mixed_en_zh_ja_es_id`, `mindy_en_es_id_pt_zh`, `kian_en_zh`, `sophie_en_zh`, `magnus_en_zh`) — NOT an ElevenLabs/cloned voice id; omit it to let the model narrate. Accepts up to 3 reference audio clips via audio_urls (reference them in the prompt as @Audio1–@Audio3) OR one reference image via image_url, plus speed/volume/pitch, output_format (wav|mp3|pcm|ogg_opus) and sample_rate. Billed per second (billed seconds ≈ ceil(text length / 15)).

{
  "max_characters": 2048,
  "max_reference_audio": 3
}

music_generation

ElevenLabs Music v1

music.elevenlabs_music_v1

Legacy prompt-based music generation up to 10 minutes — deprecated upstream (ElevenLabs marks music_v1 deprecated; music.elevenlabs_music_v2_5 is the default). Also accepts a `composition_plan` (MusicPrompt: positive/negative_global_styles + sections[] with lyrics lines) instead of a prompt; `respect_sections_durations` controls how strictly section durations are enforced.

{
  "max_duration_seconds": 600
}

music_generation

ElevenLabs Music v2

music.elevenlabs_music_v2

ElevenLabs music_v2 — the previous-generation chunk-plan model, still available when pinned. Music generation up to 10 minutes from a prompt, or a chunk-based `composition_plan` (chunks[] with text incl. [Section]/{direction} markup, duration_ms, positive/negative_styles, optional per-chunk audio conditioning refs + `seed`). Pass `store_for_inpainting` to get a `song_id` back, then reference `{ song_id, range }` chunks in a later plan to regenerate only part of the song. Also scores a `video_url` (video-to-music) when one is supplied instead of a prompt. music.elevenlabs_music_v2_5 (the default) takes the same fields.

{
  "max_duration_seconds": 600
}

music_generation

ElevenLabs Music v2.5

music.elevenlabs_music_v2_5 default

ElevenLabs music_v2_5 (default) — the newest ElevenLabs music model (released 2026-09). A prompt up to 10 minutes, or a chunk-based `composition_plan` (chunks[] with text incl. [Section]/{direction} markup, duration_ms, positive/negative_styles, optional per-chunk audio conditioning refs + `seed`), `force_instrumental`, word timestamps and inpainting (`store_for_inpainting` → `song_id`, then `{ song_id, range }` chunks). Also scores a `video_url` (video-to-music) when one is supplied instead of a prompt. Same fields as music.elevenlabs_music_v2.

{
  "max_duration_seconds": 600
}

music_generation

Lyria 3 Pro

music.lyria3_pro

Google Lyria 3 Pro — full structured songs up to 3 minutes with vocals, lyrics, and multi-language support. Accepts an optional image_url for inspiration (no negative prompting). Flat per-generation fee (duration is not configurable).

{
  "max_duration_seconds": 180,
  "prompt_max_length": 5000
}

music_generation

Lyria 3.5

music.lyria_3_5

Google Lyria 3.5 — full-length songs with vocals; writes and sings its own lyrics. Steer length and structure from the prompt (timestamps or a duration hint) — there is no duration parameter. Accepts an optional image_url for inspiration (no negative prompting). Flat per-generation fee whatever the length.

{
  "prompt_max_length": 5000
}

music_generation

MiniMax Music 3

music.minimax_music_3

MiniMax Music 3 — sings YOUR lyrics. `lyrics` is REQUIRED (structure tags like [verse]/[chorus] must each sit on their own line) alongside a `prompt` describing genre, BPM, key, vocals and arrangement. `duration_seconds` is an upper bound of 1-300 (default 60; the model may finish earlier) and billing is per second of that bound. Also accepts `num_inference_steps` (1-100, default 30), `guidance_scale` (0-20, default 1.7) and `seed`. Returns 44.1 kHz stereo WAV. No image input, no negative prompting, no composition_plan.

{
  "max_duration_seconds": 300,
  "prompt_max_length": 5000
}

sound_effect_generation

ElevenLabs SFX

sfx.elevenlabs_v1 default

Sound effect generation up to 30s.

{
  "max_duration_seconds": 30
}

transcription

OpenAI Whisper v1

transcription.whisper_v1 default

Speech-to-text with word and segment timestamps. Accepts a direct audio/video URL or a YouTube URL. Async — returns a queued run; poll genfire_get_run for the transcript.

{
  "max_file_mb": 500,
  "max_duration_seconds": 7200
}

transcription

ElevenLabs Scribe v2

transcription.elevenlabs_scribe_v2

ElevenLabs Scribe v2 speech-to-text: 90+ languages, word timestamps, speaker diarization (up to 32 speakers, `speaker_id` on every word), audio-event tags, `keyterms` vocabulary biasing (up to 1000 terms), `entity_detection` / `entity_redaction` (pii, phi, pci, …), `no_verbatim` filler removal and `detect_speaker_roles`. Same inputs as Whisper (audio_url / video_url / youtube_url); segments are grouped by speaker.

{
  "max_file_mb": 500,
  "max_duration_seconds": 7200,
  "max_speakers": 32,
  "max_keyterms": 1000
}

image_editing

BRIA Background Remove

image_edit.bria_background_remove default

Remove the background from an image. Returns a transparent PNG.

{
  "input": "image_url"
}

video_upscaling

Topaz Video Upscale

video_upscale.topaz default

Upscale a video by 2x or 4x with Topaz (Proteus). Billed per second by output resolution. Async — returns a queued run; poll genfire_get_run for completion.

{
  "scale_factors": [
    2,
    4
  ]
}

video_upscaling

Flux Video Upscale

video_upscale.flux_video_upscale

Upscale a video 1.5x–3x with FLUX 3 super-resolution. Two modes: precise (source-faithful) and creative (adds detail, accepts an optional guiding prompt). Source must be MP4, at most 20s and 50MB. Billed per second of output by resolution and mode — materially pricier than Topaz. Async — returns a queued run; poll genfire_get_run for completion.

{
  "scale_factors": [
    1.5,
    2,
    2.5,
    3
  ],
  "min_scale_factor": 1.5,
  "max_scale_factor": 3,
  "max_duration_seconds": 20,
  "max_file_mb": 50,
  "modes": [
    "precise",
    "creative"
  ]
}

image_upscaling

Topaz Upscale

image_upscale.topaz_upscale_image default

Upscale an image by 2x or 4x. Async — returns a queued run; poll genfire_get_run for completion.

{
  "scale_factors": [
    2,
    4
  ]
}

lipsync_generation

Lipsync 2 Pro

lipsync.lipsync_2_pro default

High-quality lip-sync at 480p or 720p. Default.

{
  "resolutions": [
    "480p",
    "720p"
  ]
}

lipsync_generation

Sync Lipsync v2 Pro

lipsync.sync_lipsync_v2_pro

Alternative lip-sync engine.

{
  "resolutions": [
    "480p",
    "720p"
  ]
}

lipsync_generation

Sync Lipsync v3

lipsync.sync_lipsync_v3

Premium lip-sync engine — higher fidelity than v2 Pro.

{
  "resolutions": [
    "480p",
    "720p"
  ]
}

lipsync_generation

H3 Max Lip Sync

lipsync.h3_max_lipsync

Animate a PHOTO to speech audio (send image_url instead of video_url). 480P–2K. Audio must be 5–14.8 s; longer audio is clipped at 14.8 s. No prompt — it follows the audio.

{
  "resolutions": [
    "480P",
    "768P",
    "1080P",
    "2K"
  ],
  "defaultResolution": "768P",
  "minAudioSeconds": 5,
  "maxAudioSeconds": 14.8,
  "imageAspectRatioRange": [
    0.4,
    2.5
  ]
}

model_3d_generation

Meshy v7

3d.meshy_v7 default

Convert one image (or 1–4 images of the same object from different angles) into a textured 3D model. Outputs GLB/FBX/OBJ/USDZ/BLEND/STL plus optional PBR maps, auto-rigging, and animation. Adds ultra_mode over v6 — higher-fidelity geometry with finer surface detail (single image only). Game-ready — GLB loads natively in three.js / model-viewer. Async — returns a queued run; poll genfire_get_run for the model URLs. Processing takes 5–10 minutes.

{
  "max_images": 4,
  "output_formats": [
    "glb",
    "fbx",
    "obj",
    "usdz",
    "blend",
    "stl"
  ],
  "target_polycount": {
    "min": 100,
    "max": 300000,
    "default": 30000
  },
  "topology": [
    "quad",
    "triangle"
  ],
  "ultra_mode": "single image only"
}

model_3d_generation

Meshy v6

3d.meshy_v6

Previous-generation Meshy. Same inputs and outputs as v7 minus ultra_mode — pin this only to reproduce earlier results. Converts one image (or 1–4 images of the same object from different angles) into a textured 3D model. Outputs GLB/FBX/OBJ/USDZ/BLEND/STL plus optional PBR maps, auto-rigging, and animation. Async — returns a queued run; poll genfire_get_run for the model URLs. Processing takes 5–10 minutes.

{
  "max_images": 4,
  "output_formats": [
    "glb",
    "fbx",
    "obj",
    "usdz",
    "blend",
    "stl"
  ],
  "target_polycount": {
    "min": 100,
    "max": 300000,
    "default": 30000
  },
  "topology": [
    "quad",
    "triangle"
  ]
}

game_generation

Genfire Games

game.genfire_v1 default

Describe a game and get a fully-playable browser game (self-contained HTML, Phaser/canvas/Three.js). The completed run carries a public play_url you can open or share. Iterate by passing game_id with a change prompt. Async — returns a queued run; the codegen runs on a worker (usually 1–3 min). Poll GET /runs/{id} until status is completed, then read output.play_url.

{
  "output": "playable_html",
  "shareable": true
}

app_generation

Genfire App Builder

app.genfire_v1 default

Describe an app and get a working full-stack single-file web app (UI + in-browser data layer, hosted live). The completed run carries a public live_url you can open or share. Iterate by passing app_id with a change prompt. Async — returns a queued run; codegen runs on a worker (usually 1–3 min). Poll GET /runs/{id} until status is completed, then read output.live_url.

{
  "output": "live_web_app",
  "shareable": true
}

faceless_reel_generation

Faceless Reel

reel.faceless default

End-to-end vertical (9:16) short: LLM script → voiceover → style-locked images → music → captioned video. Driven by a niche preset + visual style; cost varies with duration and music — call POST /v1/faceless-reels/estimate-cost for a per-config estimate.

{
  "aspect_ratios": [
    "9:16"
  ],
  "target_duration_sec": {
    "min": 10,
    "max": 120,
    "default": null
  },
  "music_sources": [
    "none",
    "preset",
    "ai",
    "library"
  ],
  "catalogs": [
    "presets",
    "styles",
    "music-presets",
    "caption-presets"
  ]
}

explainer_generation

Explainer

explainer.omni default

Long-form narrated explainer/documentary (20s–10min, 16:9 or 9:16): script → expressive voiceover → style-locked frames → Gemini Omni Flash video clips → composed film with optional captions. Pass `script` (structured beats) to author the entire creative contract with YOUR OWN model — narration, per-scene shot-specs, motion notes, render modes, reference routing, emphasis words and recurring cast — and Genfire only renders; or pass just `topic` and Genfire writes the script. Cost varies with duration — call POST /v1/explainers/estimate-cost first.

{
  "aspect_ratios": [
    "16:9",
    "9:16"
  ],
  "target_duration_sec": {
    "min": 20,
    "max": 600,
    "default": 60
  },
  "music_sources": [
    "none",
    "preset",
    "ai",
    "library"
  ],
  "script_beats": {
    "min": 3,
    "max": 100
  },
  "reference_images": {
    "max": 8
  },
  "cast_members": {
    "max": 3
  },
  "caption_modes": [
    "full",
    "keywords"
  ],
  "caption_positions": [
    "top",
    "middle",
    "bottom"
  ],
  "catalogs": [
    "styles"
  ]
}

music_video_generation

Music Video

music-video.omni default

Auto-produced AI music video (9:16 or 16:9) from a song: song → beat/section analysis → style-locked anchor frame → per-scene MiniMax H3 Max Turbo clips cut to the music, with performance shots lip-synced to the song itself (H3 Max Lip Sync, when the song has word timestamps) → composed video with optional karaoke lyric captions. Bring your own song via `song_url` (with optional lyrics/sections/word timestamps), or pass an inline `song` prompt and Genfire generates the track first (ElevenLabs Music v2.5, billed separately). Cost scales with song length and scene_density — call POST /v1/music-videos/estimate-cost first.

{
  "aspect_ratios": [
    "9:16",
    "16:9"
  ],
  "scene_densities": [
    "low",
    "medium",
    "high"
  ],
  "song_duration_sec": {
    "min": 1,
    "max": 300
  },
  "inline_song_duration_ms": {
    "min": 10000,
    "max": 600000
  },
  "reference_images": {
    "max": 8
  },
  "catalogs": [
    "styles"
  ]
}

picture_book_generation

Picture Book

picture-book.gpt-image-2 default

A complete illustrated picture book from a story idea or script: the free planner writes/splits the text to the age band, then a style anchor, one reference sheet per cast member, every page and both covers render on GPT Image 2 (edit endpoint, anchor + sheets as references) so characters stay consistent. Text is typeset in post (default) or lettered by the model; print formats render at KDP trim + bleed and export as print-ready PDFs (interior, cover, ebook, images zip). Async: poll GET /v1/runs/:id (progress stages included), then read GET /v1/picture-books/:id. Call POST /v1/picture-books/estimate-cost first — cost scales with pages × quality.

{
  "age_bands": [
    "board",
    "picture",
    "early-reader"
  ],
  "qualities": [
    "low",
    "medium"
  ],
  "lettering_modes": [
    "typeset",
    "lettered"
  ],
  "pages": {
    "min": 4,
    "max": 48
  },
  "cast": {
    "max": 4
  },
  "export_kinds": [
    "interior-pdf",
    "cover-pdf",
    "ebook-pdf",
    "images-zip"
  ],
  "catalogs": [
    "styles",
    "formats",
    "age_bands"
  ]
}

Outcome Layer

Workflow Catalog

Use workflows when you want a business outcome such as hook generation, a creative pack, or UGC ad variants instead of a raw media primitive.

Workflow

hook_pack

available

Generate three monetizable UGC-style hooks plus a supporting body and CTA.

Input Schema
{
  "type": "object",
  "required": [
    "prompt"
  ],
  "properties": {
    "prompt": {
      "type": "string"
    },
    "audience": {
      "type": "string"
    },
    "tone": {
      "type": "string",
      "enum": [
        "casual",
        "professional",
        "humorous",
        "educational",
        "persuasive",
        "emotional"
      ]
    },
    "length": {
      "type": "string",
      "enum": [
        "short",
        "medium",
        "long"
      ]
    },
    "target_duration": {
      "type": "number"
    },
    "keywords": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "additional_requirements": {
      "type": "string"
    }
  }
}
Output Schema
{
  "type": "object",
  "properties": {
    "hooks": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "body": {
      "type": "string"
    },
    "call_to_action": {
      "type": "string"
    },
    "full_script": {
      "type": "string"
    }
  }
}
Example Request Body
{
  "prompt": "Portable blender for busy professionals",
  "audience": "Remote workers who skip breakfast",
  "tone": "persuasive",
  "length": "short",
  "target_duration": 20,
  "keywords": [
    "protein shake",
    "commute",
    "healthy"
  ],
  "additional_requirements": "Lead with convenience and save time."
}
Example Output
{
  "workflow": "hook_pack",
  "topic": "Portable blender for busy professionals",
  "hooks": [
    "If breakfast keeps losing to your calendar, this fixes it.",
    "I stopped skipping protein the day I threw this in my bag.",
    "This is how I make a full shake before my first meeting."
  ],
  "body": "Portable, fast, and easy to clean for daily use.",
  "call_to_action": "Try it before your next busy week.",
  "full_script": "If breakfast keeps losing to your calendar...",
  "word_count": 96,
  "estimated_duration": 22
}

Workflow

ugc_ad

available

Generate an outcome-oriented UGC ad script with hooks, body, CTA, and a full spoken script.

Input Schema
{
  "type": "object",
  "required": [
    "prompt"
  ],
  "properties": {
    "prompt": {
      "type": "string"
    },
    "audience": {
      "type": "string"
    },
    "tone": {
      "type": "string",
      "enum": [
        "casual",
        "professional",
        "humorous",
        "educational",
        "persuasive",
        "emotional"
      ]
    },
    "length": {
      "type": "string",
      "enum": [
        "short",
        "medium",
        "long"
      ]
    },
    "target_duration": {
      "type": "number"
    },
    "keywords": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "additional_requirements": {
      "type": "string"
    }
  }
}
Output Schema
{
  "type": "object",
  "properties": {
    "hooks": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "body": {
      "type": "string"
    },
    "call_to_action": {
      "type": "string"
    },
    "full_script": {
      "type": "string"
    }
  }
}
Example Request Body
{
  "prompt": "Blue light glasses for creators",
  "audience": "Designers and editors working late",
  "tone": "casual",
  "length": "medium"
}
Example Output
{
  "workflow": "ugc_ad",
  "topic": "Blue light glasses for creators",
  "hooks": [
    "I did not realize how cooked my eyes were by 6pm."
  ],
  "body": "Comfort-focused UGC body copy.",
  "call_to_action": "Use them on your next edit sprint.",
  "full_script": "I did not realize how cooked my eyes were..."
}

Workflow

ugc_ad_video

available

One run → a finished UGC video ad: researches and writes the script (brand/product aware), generates the talking-avatar product shot on Seedance 2.5 (with optional avatar template voice), and burns styled captions. Async — poll the returned run for the video URL. Cost = the component script workflow + the Seedance 2.5 generation (captions are free).

Input Schema
{
  "type": "object",
  "required": [
    "prompt"
  ],
  "properties": {
    "prompt": {
      "type": "string",
      "description": "The product/offer brief the ad should sell. Required unless a finished `script` is supplied."
    },
    "script": {
      "type": "string",
      "description": "Skip script generation and use this exact spoken script."
    },
    "brand_id": {
      "type": "string",
      "description": "Ground the ad on a stored brand: its featured product photo fills the product slot and its voice/palette/words-to-avoid condition the script. The same brand_id contract as image/video generation and faceless reels."
    },
    "product_url": {
      "type": "string",
      "format": "uri",
      "description": "Product page URL — switches script generation to the product-aware creative_pack research flow."
    },
    "product_image_url": {
      "type": "string",
      "format": "uri",
      "description": "Product photo the creator presents on camera (reference image)."
    },
    "avatar_template_id": {
      "type": "string",
      "description": "Avatar from the Genfire template library — brings the face AND (by default) its voice sample."
    },
    "avatar_image_url": {
      "type": "string",
      "format": "uri",
      "description": "Custom avatar/creator face image (alternative to avatar_template_id)."
    },
    "use_avatar_voice": {
      "type": "boolean",
      "default": true,
      "description": "Condition the speech on the avatar template's voice sample when one exists."
    },
    "audience": {
      "type": "string"
    },
    "tone": {
      "type": "string",
      "enum": [
        "casual",
        "professional",
        "humorous",
        "educational",
        "persuasive",
        "emotional"
      ]
    },
    "scene": {
      "type": "string",
      "description": "Optional scene direction (e.g. \"in a car, golden hour\")."
    },
    "duration_seconds": {
      "type": "number",
      "minimum": 5,
      "maximum": 60,
      "default": 12,
      "description": "Up to 60s: ads longer than 30s chain multiple voice/face-conditioned takes with jump cuts (the native UGC edit)."
    },
    "aspect_ratio": {
      "type": "string",
      "enum": [
        "9:16",
        "16:9",
        "1:1",
        "3:4",
        "4:3",
        "21:9"
      ],
      "default": "9:16"
    },
    "resolution": {
      "type": "string",
      "enum": [
        "480p",
        "720p",
        "1080p"
      ],
      "default": "720p",
      "description": "Legacy 768p / 2k are still accepted and map to 720p / 1080p."
    },
    "captions": {
      "type": "boolean",
      "default": true
    },
    "caption_preset_id": {
      "type": "string",
      "default": "hormozi"
    },
    "caption_position": {
      "type": "string",
      "enum": [
        "top",
        "middle",
        "bottom"
      ],
      "default": "bottom"
    },
    "caption_words_per_line": {
      "type": "number",
      "minimum": 1,
      "maximum": 6
    },
    "broll": {
      "type": "string",
      "enum": [
        "none",
        "auto",
        "stock",
        "stills",
        "video"
      ],
      "default": "auto",
      "description": "Multi-scene cutaways while the avatar voice continues. auto (default) = one product-fidelity still + FREE stock clips timed to spoken keywords; stock = all free stock footage; stills = generated images; video = Seedance 2.5 clips (premium)."
    },
    "broll_count": {
      "type": "number",
      "minimum": 1,
      "maximum": 3,
      "default": 2
    },
    "reference_ad_research_id": {
      "type": "string",
      "description": "A stored competitor-ad analysis (from POST /v1/ads/analyze or the agent's analyze_competitor_ad) — the script follows that proven format for YOUR product; assets and wording are never copied."
    }
  }
}
Output Schema
{
  "type": "object",
  "properties": {
    "video_url": {
      "type": "string"
    },
    "captioned": {
      "type": "boolean"
    },
    "script": {
      "type": "string"
    },
    "hooks": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "source_video_id": {
      "type": "string"
    },
    "duration_seconds": {
      "type": "number"
    },
    "aspect_ratio": {
      "type": "string"
    }
  }
}
Example Request Body
{
  "prompt": "Blue light glasses for creators",
  "product_url": "https://example.com/products/blue-light-glasses",
  "product_image_url": "https://example.com/products/blue-light-glasses/hero.jpg",
  "avatar_template_id": "tmpl_ugc_creator_04",
  "duration_seconds": 12,
  "aspect_ratio": "9:16",
  "captions": true
}
Example Output
{
  "workflow": "ugc_ad_video",
  "video_url": "https://storage.googleapis.com/genfire/ads/final_ad.mp4",
  "captioned": true,
  "script": "I did not realize how cooked my eyes were by 6pm — these fixed it...",
  "hooks": [
    "I did not realize how cooked my eyes were by 6pm."
  ],
  "source_video_id": "vid_abc123",
  "duration_seconds": 12,
  "aspect_ratio": "9:16"
}

Workflow

ad_remix

available

One winning ad → variants with the nouns swapped, the cut and pacing kept. Engine: Gemini Omni Flash 1.1, two routes chosen per variant. EDIT (every change is words): the footage itself is edited, so the setting, framing, motion and product are kept by construction — a ≤10s source goes whole, a longer one is cut on its own hard cuts into ≤10s segments and each is edited; variants that ACCUMULATE (each one's changes extend the previous one's, in order) are CHAINED — each edits the previous variant's output with only what it adds, so a new creator carries through outfit → location → language. REFERENCE (a change shows an image): the source is split on its own hard cuts into ≤9s segments (each fed three ≤3s slices of itself as references). MIXED (words AND a picture in the same variant — "make her Persian and put her in this hoodie"): the server SPLITS it into an implicit two-step chain and still returns ONE variant — an intermediate leg (id `{id}.edit`, `intermediate: true`) renders the words-only changes on the edit route, then the deliverable renders the image-backed ones on the reference route with its slices cut from THAT LEG'S OUTPUT (`chain_from`), so a words-only person swap actually lands instead of coming back as the original person. Both legs are billed; only the deliverable appears in output.variants[], the leg appears under output.intermediates[]. Never restructure a request to work around routing — put every change on one variant and let the server split it. A creator change that carries a face image is DESCRIBED in words by the server and the picture is never sent with footage (the engine refuses a face next to footage) — the new person is a look-alike of the description; picker cards (creator portrait, outfit flat lay, empty location) are generated AFTER each video from its own frame so the card is what rendered. Every (variant × segment) is one generation, cards ~1 credit each, and the segments are concatenated on the planned boundaries with the sound the model rendered — it renders speech, ambience and music with the picture, mouths in sync, so a creator swap or a language change ({ what: "language", description: "Spanish" }) comes back dubbed with no lipsync step. The original track (or a supplied one) can be laid back on instead. Each output is cut-diffed against the source and returns a fidelity score. Async — poll the returned run; output.variants[] carries a video_url per variant. Cost = segments × variants × per-second Omni pricing at the chosen resolution — quote it first with POST /v1/ads/remix (dry run).

Input Schema
{
  "type": "object",
  "required": [
    "source_video_url"
  ],
  "properties": {
    "source_video_url": {
      "type": "string",
      "format": "uri",
      "description": "The winning ad, as a public https URL (a Genfire run output URL works). Clean sources only — burned-in captions come back as garbled text."
    },
    "product_image_url": {
      "type": "string",
      "format": "uri",
      "description": "The product, PINNED: an Element image or product photo that rides every REFERENCE generation as a fixed object reference (\"the product, exactly as shown\"). Without it the model re-draws the product from the reference slices and it drifts between variants. It is NOT sent on an edit-route call — that endpoint has no image pool, so there the product is held from the footage by the keep-list (\"exactly as it is in the video\") and the plan says so in developer_notes. A `product` CHANGE pointing at this same image is folded back into the pin rather than honoured as a change, so repeating the product photo can never push a variant onto the reference route on its own. The pin steps aside entirely for a variant that changes the product to a DIFFERENT image."
    },
    "changes": {
      "type": "array",
      "minItems": 1,
      "maxItems": 10,
      "description": "ONE variant: what to change. Use this OR `variants`.",
      "items": {
        "type": "object",
        "required": [
          "what"
        ],
        "properties": {
          "what": {
            "type": "string",
            "description": "\"creator\", \"outfit\", \"location\", \"product\" get tuned phrasing; anything else (\"the drink she is holding\", \"time of day\") is honoured generically."
          },
          "image_url": {
            "type": "string",
            "format": "uri",
            "description": "Reference image showing the replacement (an Element, a product photo, an outfit or location still). For a CREATOR change the face is described in words server-side and the picture is never sent with footage — the engine refuses that — so the result is a look-alike, not that exact face; the creator card in the output is made from the render."
          },
          "description": {
            "type": "string",
            "description": "Plain-English detail. Required when there is no image_url or preset."
          },
          "preset": {
            "type": "string",
            "description": "A named preset with the wording already written (no image slot used; `what` may be omitted). look: anime, retro-collage, bold-urban, green-contrast, warm-glow, neon-cyberpunk, claymation, comic-pop, doodle-notes, vhs-retro, paper-craft, watercolor, noir-film, hud-tracking · location: marble-kitchen, gym-floor, car-interior, golden-hour-street, cafe-terrace, beach-boardwalk, city-rooftop-dusk, bedroom-morning, home-office, city-park, airplane-wing, volcano-rim, doll-size, moving-car-roof · time: golden-hour, blue-hour, midday, overcast, night, indoor-evening · weather: light-rain, snow, fog, heat-haze · wardrobe: athleisure, streetwear, business-casual, evening-wear, cozy-knit, workwear · camera: handheld-phone, tripod-locked, slow-push-in, wider-lens · language: en, es, de, fr, it, pt-br, ja, ko, zh, hi, ar. The dry run lists them under `presets`."
          }
        }
      }
    },
    "variants": {
      "type": "array",
      "minItems": 1,
      "maxItems": 12,
      "description": "SEVERAL variants, each a full remix of the whole ad. Use this OR `changes`.",
      "items": {
        "type": "object",
        "required": [
          "changes"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "changes": {
            "type": "array",
            "minItems": 1,
            "maxItems": 10,
            "items": {
              "type": "object",
              "required": [
                "what"
              ],
              "properties": {
                "what": {
                  "type": "string",
                  "description": "\"creator\", \"outfit\", \"location\", \"product\" get tuned phrasing; anything else (\"the drink she is holding\", \"time of day\") is honoured generically."
                },
                "image_url": {
                  "type": "string",
                  "format": "uri",
                  "description": "Reference image showing the replacement (an Element, a product photo, an outfit or location still). For a CREATOR change the face is described in words server-side and the picture is never sent with footage — the engine refuses that — so the result is a look-alike, not that exact face; the creator card in the output is made from the render."
                },
                "description": {
                  "type": "string",
                  "description": "Plain-English detail. Required when there is no image_url or preset."
                },
                "preset": {
                  "type": "string",
                  "description": "A named preset with the wording already written (no image slot used; `what` may be omitted). look: anime, retro-collage, bold-urban, green-contrast, warm-glow, neon-cyberpunk, claymation, comic-pop, doodle-notes, vhs-retro, paper-craft, watercolor, noir-film, hud-tracking · location: marble-kitchen, gym-floor, car-interior, golden-hour-street, cafe-terrace, beach-boardwalk, city-rooftop-dusk, bedroom-morning, home-office, city-park, airplane-wing, volcano-rim, doll-size, moving-car-roof · time: golden-hour, blue-hour, midday, overcast, night, indoor-evening · weather: light-rain, snow, fog, heat-haze · wardrobe: athleisure, streetwear, business-casual, evening-wear, cozy-knit, workwear · camera: handheld-phone, tripod-locked, slow-push-in, wider-lens · language: en, es, de, fr, it, pt-br, ja, ko, zh, hi, ar. The dry run lists them under `presets`."
                }
              }
            }
          },
          "direction": {
            "type": "string",
            "description": "Free-text direction for this variant, appended verbatim."
          },
          "audio": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "generated",
                  "original",
                  "none"
                ]
              },
              {
                "type": "string",
                "format": "uri"
              },
              {
                "type": "object",
                "required": [
                  "mode"
                ],
                "properties": {
                  "mode": {
                    "type": "string",
                    "enum": [
                      "generated",
                      "original",
                      "replace",
                      "none"
                    ]
                  },
                  "url": {
                    "type": "string",
                    "format": "uri"
                  }
                }
              }
            ],
            "description": "\"generated\" (default — the model's own sound, in sync with its mouths), \"original\" (the source track laid back on), \"none\", an audio URL, or { mode: generated|original|replace|none, url }."
          }
        }
      }
    },
    "audio": {
      "anyOf": [
        {
          "type": "string",
          "enum": [
            "generated",
            "original",
            "none"
          ]
        },
        {
          "type": "string",
          "format": "uri"
        },
        {
          "type": "object",
          "required": [
            "mode"
          ],
          "properties": {
            "mode": {
              "type": "string",
              "enum": [
                "generated",
                "original",
                "replace",
                "none"
              ]
            },
            "url": {
              "type": "string",
              "format": "uri"
            }
          }
        }
      ],
      "description": "Run-level soundtrack default: \"generated\" (the sound the model rendered with the picture — default), \"original\" (the source track laid back on), \"none\", an http(s) audio URL, or { mode, url }. A variant's own `audio` wins."
    },
    "aspect_ratio": {
      "type": "string",
      "enum": [
        "9:16",
        "16:9"
      ],
      "default": "9:16",
      "description": "Match the source."
    },
    "resolution": {
      "type": "string",
      "enum": [
        "360p",
        "720p",
        "1080p",
        "4k"
      ],
      "default": "1080p",
      "description": "720p for drafts — cost scales ~10x across the ladder."
    },
    "cards": {
      "type": "boolean",
      "default": true,
      "description": "Make picker cards from each variant's output — a creator portrait, an outfit flat lay, an empty location — for the changes it introduces (~1 credit each). false to skip."
    }
  }
}
Output Schema
{
  "type": "object",
  "properties": {
    "engine": {
      "type": "string"
    },
    "route": {
      "type": "string",
      "enum": [
        "edit",
        "reference",
        "mixed"
      ]
    },
    "segments": {
      "type": "number"
    },
    "generation_count": {
      "type": "number"
    },
    "cards": {
      "type": "number",
      "description": "How many picker cards were planned."
    },
    "intermediates": {
      "type": "array",
      "description": "Debugging only: the words-only legs the server inserted for variants that mixed words with pictures. Each was billed and rendered; none is a deliverable — they never appear in variants[] and can never be the run's video_url.",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "completed",
              "failed"
            ]
          },
          "intermediate": {
            "type": "boolean"
          },
          "video_url": {
            "type": "string"
          },
          "chain_from": {
            "type": "string"
          },
          "error": {
            "type": "string"
          }
        }
      }
    },
    "variants": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "completed",
              "failed"
            ]
          },
          "video_url": {
            "type": "string"
          },
          "duration_seconds": {
            "type": "number"
          },
          "qa": {
            "type": "object",
            "description": "Cut-diff against the source: fidelity 0-1, matched/missing/extra cuts, mean drift."
          },
          "audio_track": {
            "type": "string",
            "enum": [
              "generated",
              "original",
              "replace",
              "none"
            ],
            "description": "Which soundtrack shipped on this variant."
          },
          "thumbnail_url": {
            "type": "string"
          },
          "cards": {
            "type": "object",
            "description": "Picker cards made from THIS output: { creator?, outfit?, location? } → image URL. The card is what rendered."
          },
          "chain_from": {
            "type": "string",
            "description": "The variant this one was rendered on top of — an earlier variant it accumulates on, or the intermediate leg the server inserted when this variant mixed words-only changes with image-backed ones."
          },
          "creator_reference_url": {
            "type": "string",
            "description": "The face the caller picked for the creator change — described, never sent to the engine."
          },
          "library_video_id": {
            "type": "string"
          },
          "error": {
            "type": "string"
          }
        }
      }
    }
  }
}

Workflow

creative_pack

available

Build a product-aware creative pack with analysis, angles, hooks, and a monetizable script.

Input Schema
{
  "type": "object",
  "properties": {
    "prompt": {
      "type": "string"
    },
    "product_url": {
      "type": "string",
      "format": "uri"
    },
    "product_name": {
      "type": "string"
    },
    "product_description": {
      "type": "string"
    },
    "category": {
      "type": "string"
    },
    "audience": {
      "type": "string"
    },
    "tone": {
      "type": "string",
      "enum": [
        "casual",
        "professional",
        "humorous",
        "educational",
        "persuasive",
        "emotional"
      ]
    },
    "length": {
      "type": "string",
      "enum": [
        "short",
        "medium",
        "long"
      ]
    },
    "image_urls": {
      "type": "array",
      "items": {
        "type": "string",
        "format": "uri"
      }
    },
    "additional_requirements": {
      "type": "string"
    }
  }
}
Output Schema
{
  "type": "object",
  "properties": {
    "product": {
      "type": "object"
    },
    "analysis": {
      "type": "object"
    },
    "hooks": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "body": {
      "type": "string"
    },
    "call_to_action": {
      "type": "string"
    },
    "full_script": {
      "type": "string"
    }
  }
}
Example Request Body
{
  "product_url": "https://example.com/products/matte-black-water-bottle"
}
Example Output
{
  "workflow": "creative_pack",
  "product": {
    "name": "Matte Black Water Bottle",
    "description": "Insulated stainless steel water bottle",
    "category": "hydration",
    "price": "$39",
    "images": [
      "https://example.com/image-1.jpg"
    ]
  },
  "analysis": {
    "marketingAngles": [
      "Premium feel",
      "Keeps drinks cold",
      "Daily carry convenience"
    ]
  },
  "hooks": [
    "This bottle still had ice after my entire workout and commute."
  ],
  "body": "Creative body copy built from product analysis.",
  "call_to_action": "Upgrade your daily carry.",
  "full_script": "This bottle still had ice..."
}

Workflow

product_ad_variants

available

Generate multiple UGC-style ad script variants for the same product or offer.

Input Schema
{
  "type": "object",
  "properties": {
    "prompt": {
      "type": "string"
    },
    "product_name": {
      "type": "string"
    },
    "product_description": {
      "type": "string"
    },
    "audience": {
      "type": "string"
    },
    "tone": {
      "type": "string",
      "enum": [
        "casual",
        "professional",
        "humorous",
        "educational",
        "persuasive",
        "emotional"
      ]
    },
    "length": {
      "type": "string",
      "enum": [
        "short",
        "medium",
        "long"
      ]
    },
    "variant_count": {
      "type": "number",
      "minimum": 1,
      "maximum": 5
    },
    "additional_requirements": {
      "type": "string"
    }
  }
}
Output Schema
{
  "type": "object",
  "properties": {
    "variant_count": {
      "type": "number"
    },
    "variants": {
      "type": "array",
      "items": {
        "type": "object"
      }
    }
  }
}
Example Request Body
{
  "product_name": "Portable espresso maker",
  "product_description": "Travel-friendly espresso maker for commuters",
  "audience": "Commuters and travelers",
  "tone": "persuasive",
  "length": "short",
  "variant_count": 3
}
Example Output
{
  "workflow": "product_ad_variants",
  "prompt": "Portable espresso maker: Travel-friendly espresso maker for commuters",
  "variant_count": 3,
  "variants": [
    {
      "index": 0,
      "strategy": "Question-led hook with direct pain point.",
      "hooks": [
        "Why are you still settling for station coffee?"
      ],
      "body": "Variant body",
      "call_to_action": "Carry café-level espresso anywhere.",
      "full_script": "Why are you still settling for station coffee?"
    }
  ]
}

Reference

Authentication

POST /oauth/token

Issue an OAuth access token

Use OAuth client credentials for machine-to-machine access. API keys are supported on the same `/v1` endpoints and are usually the simplest option.

Auth: No auth
Idempotency-Key: Not required

Body Fields

grant_type string required

Must be `client_credentials`.

client_id string required

The OAuth client id created in the developer portal.

client_secret string required

The OAuth client secret shown only when the client is created.

scope string optional

Optional space-separated scopes. Omit to receive the client’s default scopes.

curl Example

curl https://api.genfire.ai/v1/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
  "grant_type": "client_credentials",
  "client_id": "gfo_oauth_123",
  "client_secret": "gfs_live_secret",
  "scope": "models:read runs:read images:write"
}'

Response Example

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.example",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "models:read runs:read images:write"
}

Request Example Body

{
  "grant_type": "client_credentials",
  "client_id": "gfo_oauth_123",
  "client_secret": "gfs_live_secret",
  "scope": "models:read runs:read images:write"
}

Notes

  • You can also send `client_id:client_secret` with HTTP Basic auth instead of putting them in JSON.
  • The returned access token is used in the same `Authorization: Bearer ...` header as an API key.

Reference

CLI Auth

POST /cli/auth/sessions

Start a CLI auth session

Begins the browser-based PKCE handshake used by the Genfire CLI. The CLI generates a 43-128 character base64url `code_verifier`, derives `code_challenge = BASE64URL(SHA256(verifier))`, calls this endpoint, and opens `verification_url` in the user’s browser.

Auth: No auth
Idempotency-Key: Not required

Body Fields

client_id string required

Public CLI client id. Currently only `genfire-cli`.

code_challenge string required

BASE64URL(SHA256(code_verifier)). 43-128 characters.

code_challenge_method string required

Must be `S256`.

scopes array optional

Optional list of scopes the CLI is requesting. Omit for the default key scopes.

label string optional

Optional human label shown to the user on the consent page (e.g. `genfire-cli (laptop)`).

curl Example

curl https://api.genfire.ai/v1/cli/auth/sessions \
  -H "Content-Type: application/json" \
  -d '{
  "client_id": "genfire-cli",
  "code_challenge": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
  "code_challenge_method": "S256",
  "scopes": [
    "account:read",
    "credits:read",
    "models:read",
    "runs:read",
    "images:write",
    "videos:write",
    "uploads:write"
  ],
  "label": "genfire-cli (laptop)"
}'

Response Example

{
  "session_id": "clisess_8f3c4a2b1d6e7f9a0b1c2d3e",
  "verification_url": "https://genfire.ai/cli-auth?session=clisess_8f3c4a2b1d6e7f9a0b1c2d3e",
  "expires_at": "2026-05-04T17:21:25.985Z",
  "requested_scopes": [
    "account:read",
    "images:write",
    "uploads:write"
  ],
  "label": "genfire-cli (laptop)"
}

Request Example Body

{
  "client_id": "genfire-cli",
  "code_challenge": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
  "code_challenge_method": "S256",
  "scopes": [
    "account:read",
    "credits:read",
    "models:read",
    "runs:read",
    "images:write",
    "videos:write",
    "uploads:write"
  ],
  "label": "genfire-cli (laptop)"
}

Notes

  • Sessions expire after 10 minutes if not approved.
  • Poll `GET /cli/auth/sessions/{sessionId}` to discover when the user has approved (or denied) the session.
GET /cli/auth/sessions/{sessionId}

Poll the status of a CLI auth session

Returns the current status of a CLI auth session. The CLI polls this endpoint while the user completes the browser flow.

Auth: No auth
Idempotency-Key: Not required

Parameters

sessionId path required

Session id returned from `POST /cli/auth/sessions`.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/cli/auth/sessions/{sessionId}

Response Example

{
  "session_id": "clisess_8f3c4a2b1d6e7f9a0b1c2d3e",
  "status": "approved",
  "label": "genfire-cli (laptop)",
  "requested_scopes": [
    "account:read",
    "images:write",
    "uploads:write"
  ],
  "expires_at": "2026-05-04T17:21:25.985Z"
}

Notes

  • Status transitions: `pending` → `approved` (or `denied`) → `consumed` (after exchange).
  • A `consumed` or `expired` status means the CLI must restart the login flow.
POST /cli/auth/sessions/{sessionId}/exchange

Exchange an approved CLI auth session for an API key

Once `GET /cli/auth/sessions/{sessionId}` reports `approved`, the CLI submits its original `code_verifier` here. The server verifies `SHA256(code_verifier) === code_challenge`, then returns the freshly minted API key exactly once.

Auth: No auth
Idempotency-Key: Not required

Parameters

sessionId path required

Session id returned from `POST /cli/auth/sessions`.

Body Fields

code_verifier string required

The original PKCE code_verifier the CLI generated when starting the session.

curl Example

curl https://api.genfire.ai/v1/cli/auth/sessions/{sessionId}/exchange \
  -H "Content-Type: application/json" \
  -d '{
  "code_verifier": "M25iVXpKU3puUjFaYWg3T1NDTDQtcW1ROUY5YXlwalNoc0hhakxifmZHag"
}'

Response Example

{
  "api_key": "gfa_live_abc123.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "scopes": [
    "account:read",
    "images:write",
    "uploads:write"
  ],
  "label": "genfire-cli (laptop)",
  "session_expires_at": "2026-05-04T17:21:25.985Z"
}

Request Example Body

{
  "code_verifier": "M25iVXpKU3puUjFaYWg3T1NDTDQtcW1ROUY5YXlwalNoc0hhakxifmZHag"
}

Notes

  • The `api_key` is returned exactly once. Store it securely (the CLI uses the OS keychain).
  • Sessions are single-use. Once exchanged, the same session cannot be exchanged again.
  • A 428 response means the user has not yet approved the session — keep polling.
  • A 410 response means the session expired or was already exchanged. Restart the CLI login.

Reference

Account

GET /account
account:read

Get the authenticated account

Returns the public API account linked to the calling API key or OAuth client, plus `min_supported_plugin_versions` — the minimum build of each official Genfire plugin (`genfire-photoshop`, `genfire-premiere`, `genfire-aftereffects`, `genfire-blender`, `genfire-resolve`, `genfire-cli`) this API still supports.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/account \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "id": "acct_user_123",
  "object": "account",
  "display_name": "Genfire Developer",
  "email": "owner@example.com",
  "plan": "pro",
  "status": "active",
  "min_supported_plugin_versions": {
    "genfire-cli": "0.0.0",
    "genfire-photoshop": "0.0.0",
    "genfire-premiere": "0.0.0",
    "genfire-aftereffects": "0.0.0",
    "genfire-blender": "0.0.0",
    "genfire-resolve": "0.0.0"
  },
  "created_at": "2026-03-25T12:00:00.000Z",
  "updated_at": "2026-03-25T12:00:00.000Z"
}

Notes

  • Desktop plugins should call this at startup and compare their own version against their entry in `min_supported_plugin_versions`. A build below its floor should tell the user to update rather than continue and fail cryptically later. `0.0.0` means no floor is set for that client.
  • Send `User-Agent: genfire-<host>/<version> (<hostName>/<hostVersion>; <platform>)` — e.g. `genfire-photoshop/1.2.0 (photoshop/26.1; darwin)`. It is recorded per request so per-client usage and failures can be attributed.
GET /account/credits
credits:read

Get the current credit balance

Shows the credit balance for the authenticated account. Pass `team_id` to read a WORKSPACE credit pool instead — that is the balance a run carrying the same `team_id` will actually draw from, and it also returns your own month-to-date usage and monthly cap on that workspace, which can block a run even when the pool is healthy.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

team_id query optional

Read this workspace's shared pool instead of the personal balance (`GET /v1/teams`). Requires the `teams:read` scope; 404 `invalid_team_id` when you are not a member.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/account/credits \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "account_id": "acct_user_123",
  "balance": 250,
  "currency": "credits"
}

Notes

  • With `team_id` the response additionally carries `team_name`, `my_usage_this_month`, `my_cap`, `my_remaining` and `role`. Without it, `team_id` is null and `balance` is the personal balance.

Reference

Discovery

GET /models
models:read

List public model aliases

Use model aliases from this endpoint instead of internal provider ids. The aliases are stable and safe to pass back into generation endpoints.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/models \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "image.genfire_v1",
      "object": "model",
      "capability": "image_generation",
      "name": "Genfire v1",
      "description": "Genfire's signature image model — tuned for ad/product creative.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4"
        ],
        "max_count": 4,
        "max_reference_images": 16
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": true,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.nano_banana_2",
      "object": "model",
      "capability": "image_generation",
      "name": "Nano Banana 2",
      "description": "Google's general-purpose image model. Fast, broad subject coverage. Superseded by image.nano_banana_2_1 (same price).",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4",
          "21:9",
          "9:21"
        ],
        "max_count": 4
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": true,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.nano_banana_2_1",
      "object": "model",
      "capability": "image_generation",
      "name": "Nano Banana 2.1",
      "description": "Google's newest Nano Banana — better visual design, mask-based editing and subject consistency than Nano Banana 2, same price. 1K/2K/4K. Default.",
      "status": "available",
      "is_default": true,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4",
          "21:9"
        ],
        "max_count": 4,
        "resolutions": [
          "1K",
          "2K",
          "4K"
        ]
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": true,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.nano_banana_pro",
      "object": "model",
      "capability": "image_generation",
      "name": "Nano Banana Pro",
      "description": "Premium photoreal image generation, higher fidelity than Nano Banana 2.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4"
        ],
        "max_count": 4
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": true,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.nano_banana",
      "object": "model",
      "capability": "image_generation",
      "name": "Nano Banana",
      "description": "Standard speed and quality. Cheaper than Pro for high-volume use.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4"
        ],
        "max_count": 4
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.nano_banana_lite",
      "object": "model",
      "capability": "image_generation",
      "name": "Nano Banana Lite",
      "description": "Gemini 3.1 Flash Lite Image — ultra-low latency, cheapest tier. Supports editing.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4",
          "21:9",
          "9:21"
        ],
        "max_count": 4
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.nano_banana_2_lite",
      "object": "model",
      "capability": "image_generation",
      "name": "Nano Banana 2 Lite",
      "description": "Gemini 3.1 Flash Lite Image (v2) — ultra-low latency text-to-image. No edit endpoint.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4",
          "21:9",
          "9:21"
        ],
        "max_count": 4
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": false,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.gpt_image_2",
      "object": "model",
      "capability": "image_generation",
      "name": "GPT Image 2",
      "description": "OpenAI GPT Image 2 — premium photorealistic generation.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4"
        ],
        "max_count": 4,
        "max_reference_images": 16
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": true,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.gpt_image_25_sunburst",
      "object": "model",
      "capability": "image_generation",
      "name": "GPT Image 2.5 Sunburst",
      "description": "OpenAI GPT Image 2.5 Sunburst — the precision tier: extra fidelity on intricate detail and tighter control across edits, at longer generation times. Same controls and price as image.gpt_image_2; use Flare when speed matters.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4"
        ],
        "max_count": 4,
        "max_reference_images": 16
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": true,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.gpt_image_25_flare",
      "object": "model",
      "capability": "image_generation",
      "name": "GPT Image 2.5 Flare",
      "description": "OpenAI GPT Image 2.5 Flare — OpenAI's recommended default: higher quality than GPT Image 2 at ~50% lower latency, for everyday, social, product and high-volume work. Same controls and price as image.gpt_image_2.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4"
        ],
        "max_count": 4,
        "max_reference_images": 16
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": true,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.flux_lora",
      "object": "model",
      "capability": "image_generation",
      "name": "Flux (trained style)",
      "description": "Flux with one of YOUR trained image styles (from POST /v1/image-styles or a style skill). Pass `loras: [{ id, scale? }]` with 1–3 completed image-style ids; the trigger word is added to the prompt automatically. Refuses to run without an adapter. Text-to-image only.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4"
        ],
        "max_count": 4
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": false,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.seedream_v45",
      "object": "model",
      "capability": "image_generation",
      "name": "Seedream 4.5",
      "description": "ByteDance Seedream 4.5 — strong product and ad visuals, photoreal.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16"
        ],
        "max_count": 4
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.seedream_v5_lite",
      "object": "model",
      "capability": "image_generation",
      "name": "Seedream 5.0 Lite",
      "description": "ByteDance Seedream 5.0 Lite — multilingual text rendering, web-search-aware.",
      "status": "preview",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16"
        ],
        "max_count": 4
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.seedream_v5_pro",
      "object": "model",
      "capability": "image_generation",
      "name": "Seedream 5.0 Pro",
      "description": "ByteDance Seedream 5.0 Pro — flagship photoreal image + multi-reference edit, up to 2K, up to 10 reference images.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16"
        ],
        "max_count": 6
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.qwen_image_2",
      "object": "model",
      "capability": "image_generation",
      "name": "Qwen Image 2",
      "description": "Alibaba Qwen Image 2 — strong typography, posters, 2K generation.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4"
        ],
        "max_count": 4
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.qwen_image_2_pro",
      "object": "model",
      "capability": "image_generation",
      "name": "Qwen Image 2 Pro",
      "description": "Premium Qwen Image 2 with enhanced detail.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4"
        ],
        "max_count": 4
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.qwen_image_3",
      "object": "model",
      "capability": "image_generation",
      "name": "Qwen Image 3",
      "description": "Alibaba Qwen Image 3 — bilingual (CN/EN) text rendering up to 2K. Edit mode takes 1-3 reference images, addressed as \"image 1\"..\"image 3\" in the prompt.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4"
        ],
        "max_count": 4,
        "max_prompt_chars": 800,
        "max_reference_images": 3
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.z_image_turbo",
      "object": "model",
      "capability": "image_generation",
      "name": "Z-Image Turbo",
      "description": "Alibaba Z-Image Turbo — 8-step distilled 6B model: photoreal output and clean EN/CN text in about a second, at the lowest price in the catalog. With `image_url` it runs image-to-image (`strength` 0-1, default 0.6 — the prompt describes the WHOLE result, not an edit instruction); add `mask_url` to inpaint only the white region.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4",
          "3:2",
          "2:3",
          "21:9"
        ],
        "max_count": 4,
        "max_reference_images": 1
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": true,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.z_image_base",
      "object": "model",
      "capability": "image_generation",
      "name": "Z-Image Base",
      "description": "Alibaba Z-Image Base — the undistilled foundation model (28 steps). Slower than Turbo but with wider stylistic range, real guidance control and `negative_prompt`. Text-to-image only.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4",
          "3:2",
          "2:3",
          "21:9"
        ],
        "max_count": 4
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": false,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.z_image_controlnet",
      "object": "model",
      "capability": "image_generation",
      "name": "Z-Image Turbo ControlNet",
      "description": "Z-Image Turbo guided by the STRUCTURE of a control image. `image_url` is required; `control_mode` picks what is extracted from it — `canny` (edges, default), `depth`, `pose`, or `none` when the image is already a control map. `control_scale` (0-1, default 0.75) sets how tightly the result follows it.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4",
          "3:2",
          "2:3",
          "21:9"
        ],
        "max_count": 4,
        "max_reference_images": 1
      },
      "capabilities": {
        "text_to_output": false,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.z_image_tiling",
      "object": "model",
      "capability": "image_generation",
      "name": "Z-Image Turbo Tiling",
      "description": "Seamlessly tileable textures and patterns from Z-Image Turbo — edges wrap with no visible seam. `tiling_mode`: `both` (default), `horizontal` or `vertical`. Optional `image_url` re-tiles an existing image; add `mask_url` to repaint part of it.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4"
        ],
        "max_count": 4,
        "max_reference_images": 1
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": true,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.recraft_v4_vector",
      "object": "model",
      "capability": "image_generation",
      "name": "Recraft V4 Vector",
      "description": "Recraft V4 vector output — a true SVG rather than a raster image. Logos, icons and flat illustration that has to scale cleanly.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4"
        ],
        "max_count": 4
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": false,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.recraft_v4_pro_vector",
      "object": "model",
      "capability": "image_generation",
      "name": "Recraft V4 Pro Vector",
      "description": "Premium Recraft V4 vector output — true SVG at the Pro tier’s fidelity.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4"
        ],
        "max_count": 4
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": false,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.recraft_v41",
      "object": "model",
      "capability": "image_generation",
      "name": "Recraft V4.1",
      "description": "Recraft V4.1 — sharper prompt control and cleaner composition than V4. Tuned for brand systems and editorial work.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4"
        ],
        "max_count": 4
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": false,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.recraft_v41_pro",
      "object": "model",
      "capability": "image_generation",
      "name": "Recraft V4.1 Pro",
      "description": "Recraft V4.1 at the Pro tier — high-resolution raster for large-format and production work.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4"
        ],
        "max_count": 4
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": false,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.recraft_v41_vector",
      "object": "model",
      "capability": "image_generation",
      "name": "Recraft V4.1 Vector",
      "description": "Recraft V4.1 vector output — true SVG.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4"
        ],
        "max_count": 4
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": false,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.recraft_v41_pro_vector",
      "object": "model",
      "capability": "image_generation",
      "name": "Recraft V4.1 Pro Vector",
      "description": "Recraft V4.1 Pro vector output — true SVG at the Pro tier’s fidelity.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4"
        ],
        "max_count": 4
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": false,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.recraft_v41_utility",
      "object": "model",
      "capability": "image_generation",
      "name": "Recraft V4.1 Utility",
      "description": "A faster, lighter V4.1 runtime for high-volume work — ideation, A/B exploration and content pipelines. Same price and parameters as image.recraft_v41; the difference is throughput.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4"
        ],
        "max_count": 4
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": false,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.recraft_v41_utility_pro",
      "object": "model",
      "capability": "image_generation",
      "name": "Recraft V4.1 Utility Pro",
      "description": "V4.1 Pro output on the faster Utility runtime — large-format work at pipeline scale. Same price and parameters as image.recraft_v41_pro.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4"
        ],
        "max_count": 4
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": false,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.recraft_v4_style",
      "object": "model",
      "capability": "image_generation",
      "name": "Recraft V4 Styles",
      "description": "Recraft V4 with a custom style. Supply a custom style either as `style_id` (trained earlier) or as 1-10 `image_urls` style references, which trains one inline and returns its `style_id` for reuse.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4"
        ],
        "max_count": 4,
        "max_reference_images": 10
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.recraft_v4_style_pro",
      "object": "model",
      "capability": "image_generation",
      "name": "Recraft V4 Styles Pro",
      "description": "Recraft V4 Styles at the Pro tier. Supply a custom style either as `style_id` (trained earlier) or as 1-10 `image_urls` style references, which trains one inline and returns its `style_id` for reuse.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4"
        ],
        "max_count": 4,
        "max_reference_images": 10
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.recraft_v4_style_vector",
      "object": "model",
      "capability": "image_generation",
      "name": "Recraft V4 Styles Vector",
      "description": "Recraft V4 Styles with true SVG output. Supply a custom style either as `style_id` (trained earlier) or as 1-10 `image_urls` style references, which trains one inline and returns its `style_id` for reuse.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4"
        ],
        "max_count": 4,
        "max_reference_images": 10
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.recraft_v4_style_pro_vector",
      "object": "model",
      "capability": "image_generation",
      "name": "Recraft V4 Styles Pro Vector",
      "description": "Recraft V4 Styles, Pro tier, true SVG output. Supply a custom style either as `style_id` (trained earlier) or as 1-10 `image_urls` style references, which trains one inline and returns its `style_id` for reuse.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4"
        ],
        "max_count": 4,
        "max_reference_images": 10
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.recraft_v4",
      "object": "model",
      "capability": "image_generation",
      "name": "Recraft V4",
      "description": "Design-grade image generation with reliable text rendering.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4"
        ],
        "max_count": 4
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": false,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.recraft_v4_pro",
      "object": "model",
      "capability": "image_generation",
      "name": "Recraft V4 Pro",
      "description": "Premium Recraft V4 — top quality for marketing/branding visuals.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4"
        ],
        "max_count": 4
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": false,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.grok_imagine",
      "object": "model",
      "capability": "image_generation",
      "name": "Grok Imagine",
      "description": "xAI Grok Imagine — stylized, strong prompt following.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16"
        ],
        "max_count": 4
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.grok_imagine_pro",
      "object": "model",
      "capability": "image_generation",
      "name": "Grok Imagine Pro",
      "description": "xAI Grok Imagine Pro (Quality mode) — sharper detail, stronger text rendering, up to 2K.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4",
          "3:2",
          "2:3",
          "2:1",
          "1:2",
          "20:9",
          "9:20",
          "19.5:9",
          "9:19.5"
        ],
        "max_count": 4,
        "resolutions": [
          "1K",
          "2K"
        ]
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.grok_imagine_2",
      "object": "model",
      "capability": "image_generation",
      "name": "Grok Imagine 2.0",
      "description": "xAI Grok Imagine 2.0 — the latest Grok Imagine generation. Up to 2K, 13 aspect ratios, and a quality tier (low | medium, default medium). Edit accepts up to 3 reference images.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4",
          "3:2",
          "2:3",
          "2:1",
          "1:2",
          "20:9",
          "9:20",
          "19.5:9",
          "9:19.5"
        ],
        "max_count": 4,
        "resolutions": [
          "1K",
          "2K"
        ],
        "qualities": [
          "low",
          "medium"
        ],
        "max_input_images": 3
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.muse_image",
      "object": "model",
      "capability": "image_generation",
      "name": "Muse Image",
      "description": "Meta Muse Image — faithful instruction-following with accurate fine detail (text, plots, QR codes). Edit mode does precise, localized changes and composes from up to 10 reference images. No resolution or quality control; omitting aspect_ratio lets Muse size the output from the prompt.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "21:9",
          "16:9",
          "4:3",
          "3:2",
          "1:1",
          "2:3",
          "3:4",
          "9:16",
          "9:21"
        ],
        "max_count": 4,
        "max_reference_images": 10
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "image.ideogram_v4_5",
      "object": "model",
      "capability": "image_generation",
      "name": "Ideogram 4.5",
      "description": "Ideogram 4.5 — posters, logos and designs with accurate typography: quote the exact words and mention the text early in the prompt. Quality low | medium | high (default medium), each its own price; 1K | 2K (default 2K), same price. Edit reads the first image as the source plus up to 4 references (3 with a mask), takes mask_url, and edit_precision: \"high\" (Precise Edit) restores the pixels the edit did not touch, at no extra cost.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "1:1",
          "16:9",
          "9:16",
          "4:3",
          "3:4",
          "3:2",
          "2:3",
          "5:4",
          "4:5",
          "16:10",
          "10:16",
          "2:1",
          "1:2",
          "3:1",
          "1:3"
        ],
        "max_count": 4,
        "resolutions": [
          "1K",
          "2K"
        ],
        "qualities": [
          "low",
          "medium",
          "high"
        ],
        "max_reference_images": 5,
        "edit_precisions": [
          "regular",
          "high"
        ]
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": true,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.flux_3",
      "object": "model",
      "capability": "video_generation",
      "name": "Flux 3",
      "description": "Black Forest Labs Flux 3 — frontier video with synchronized audio included at no extra cost. Routes to text/image-to-video, first-last-frame, keyframe pinning (keyframes[]), or extend (source_video_url). 5-20s, 720p or 1080p.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "auto",
          "21:9",
          "2:1",
          "16:9",
          "4:3",
          "1:1",
          "3:4",
          "9:16"
        ],
        "duration_seconds": [
          5,
          6,
          7,
          8,
          9,
          10,
          11,
          12,
          13,
          14,
          15,
          16,
          17,
          18,
          19,
          20
        ],
        "resolutions": [
          "720p",
          "1080p"
        ],
        "supports_audio": true,
        "max_keyframes": 10,
        "source_video_max_seconds": 15,
        "source_video_max_mb": 50
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": false,
        "source_video": true,
        "motion_control": false,
        "first_last_frame": true,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": true,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.flux_3_draft",
      "object": "model",
      "capability": "video_generation",
      "name": "Flux 3 Draft",
      "description": "Cheap 720p Flux 3 preview (~3.5x less than full quality). Returns a draft_cache_url alongside the video; pass that back to this model to render the same shot at full 1080p. Same four routing modes as Flux 3.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "auto",
          "21:9",
          "2:1",
          "16:9",
          "4:3",
          "1:1",
          "3:4",
          "9:16"
        ],
        "duration_seconds": [
          5,
          6,
          7,
          8,
          9,
          10,
          11,
          12,
          13,
          14,
          15,
          16,
          17,
          18,
          19,
          20
        ],
        "resolutions": [
          "720p"
        ],
        "supports_audio": true,
        "max_keyframes": 10,
        "source_video_max_seconds": 15,
        "source_video_max_mb": 50
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": false,
        "source_video": true,
        "motion_control": false,
        "first_last_frame": true,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": true,
        "video_to_video_reference": false,
        "draft_enhance": true
      }
    },
    {
      "id": "video.flux_3_edit",
      "object": "model",
      "capability": "video_generation",
      "name": "Flux 3 Edit",
      "description": "Flux 3 video edit — re-renders a source clip from a text prompt while preserving its motion, timing and framing (restyle, regrade, change the weather, wardrobe or set). Requires source_video_url (MP4, under 15s and 50 MB); output is 720p at the clip's own length. No duration, aspect-ratio or resolution controls. Far cheaper per second than a fresh Flux 3 render.",
      "status": "available",
      "is_default": false,
      "limits": {
        "source_video_max_seconds": 15,
        "source_video_max_mb": 50,
        "resolutions": [
          "720p"
        ]
      },
      "capabilities": {
        "text_to_output": false,
        "image_to_output": false,
        "reference_images": false,
        "source_video": true,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.veo_3_1",
      "object": "model",
      "capability": "video_generation",
      "name": "Veo 3.1",
      "description": "Google Veo 3.1 — premium text/image-to-video with native audio. Supports first-last-frame interpolation. Default.",
      "status": "available",
      "is_default": true,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          4,
          5,
          8
        ],
        "supports_audio": true
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": true,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.veo_3_1_fast",
      "object": "model",
      "capability": "video_generation",
      "name": "Veo 3.1 Fast",
      "description": "Cheaper, faster Veo 3.1 variant. Supports first-last-frame interpolation.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          4,
          5,
          8
        ],
        "supports_audio": true
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": true,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.veo_3_1_lite",
      "object": "model",
      "capability": "video_generation",
      "name": "Veo 3.1 Lite",
      "description": "Lower-cost Lite variant of Veo 3.1. Supports first-last-frame interpolation.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          4,
          5,
          8
        ],
        "supports_audio": true
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": true,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.veo_3_1_extend",
      "object": "model",
      "capability": "video_generation",
      "name": "Veo 3.1 Extend",
      "description": "Extend an existing Veo-created video by up to 7 seconds. Requires source_video_url.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16"
        ],
        "duration_seconds": [
          7
        ],
        "supports_audio": true
      },
      "capabilities": {
        "text_to_output": false,
        "image_to_output": false,
        "reference_images": false,
        "source_video": true,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.veo_3",
      "object": "model",
      "capability": "video_generation",
      "name": "Veo 3",
      "description": "Veo 3 — high-quality cinematic generation with audio (predecessor to Veo 3.1).",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16"
        ],
        "duration_seconds": [
          8
        ],
        "supports_audio": true
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.gemini_omni_flash_v11",
      "object": "model",
      "capability": "video_generation",
      "name": "Gemini Omni Flash 1.1",
      "description": "Google Gemini Omni Flash 1.1 — adds a 360p-4k resolution ladder, an end-frame on image-to-video, and reference video clips. 3-10s, 16:9 or 9:16. Control pacing and audio in the prompt. Always generates its own audio (speech, ambience, music) — direct it in the prompt; `supports_audio: false` in limits means only that there is no separate audio on/off toggle, not that output is silent.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16"
        ],
        "duration_seconds": [
          3,
          4,
          5,
          6,
          7,
          8,
          9,
          10
        ],
        "resolutions": [
          "360p",
          "720p",
          "1080p",
          "4k"
        ],
        "reference_image_urls": 10,
        "reference_video_urls": 3,
        "supports_audio": false
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": true,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": true,
        "reference_media": true,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.gemini_omni_flash",
      "object": "model",
      "capability": "video_generation",
      "name": "Gemini Omni Flash",
      "description": "Google Gemini Omni Flash — fast, low-cost text/image-to-video. Supports reference images and video-to-video editing. 3-10s, 16:9 or 9:16. Control pacing and audio in the prompt. Always generates its own audio (speech, ambience, music) — direct it in the prompt; `supports_audio: false` in limits means only that there is no separate audio on/off toggle, not that output is silent.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16"
        ],
        "duration_seconds": [
          3,
          4,
          5,
          6,
          7,
          8,
          9,
          10
        ],
        "supports_audio": false
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": true,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.veo_3_fast",
      "object": "model",
      "capability": "video_generation",
      "name": "Veo 3 Fast",
      "description": "Cheaper Veo 3 variant — same quality tier, faster turnaround.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16"
        ],
        "duration_seconds": [
          8
        ],
        "supports_audio": true
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.kling_o3",
      "object": "model",
      "capability": "video_generation",
      "name": "Kling O3 Standard",
      "description": "Kling O3 standard — smooth motion, strong subject consistency.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          5,
          10
        ],
        "supports_audio": true
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": true,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": true,
        "draft_enhance": false
      }
    },
    {
      "id": "video.kling_o3_pro",
      "object": "model",
      "capability": "video_generation",
      "name": "Kling O3 Pro",
      "description": "Pro tier of Kling O3 — better quality at higher cost. Supports video-to-video reference (use source_video_url + reference_image_urls).",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          5,
          10
        ],
        "supports_audio": true
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": true,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": true,
        "draft_enhance": false
      }
    },
    {
      "id": "video.kling_o3_4k",
      "object": "model",
      "capability": "video_generation",
      "name": "Kling O3 4K",
      "description": "Kling O3 at native 4K. Higher cost, top-tier quality. Also video-to-video at 4K: `source_video_url` + `reference_image_urls` restyles the clip while binding the references (3-15s, `duration` chosen by you); `source_video_url` ALONE is a source-preserving edit — same length and framing as the source (.mp4/.mov, 3-15s, 720-3840px, ≤200MB), billed per second of the SOURCE clip.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          5,
          10
        ],
        "resolution": "4k",
        "supports_audio": true
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": true,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": true,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": true,
        "draft_enhance": false
      }
    },
    {
      "id": "video.kling_v3",
      "object": "model",
      "capability": "video_generation",
      "name": "Kling V3 Standard",
      "description": "Kling V3 standard — text or image to video, 3-15s, with generated audio, an end frame on image-to-video, and multi-shot prompts. Use video.kling_v3_turbo for the faster, cheaper tier and video.kling_o3 for reference-to-video.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          3,
          4,
          5,
          6,
          7,
          8,
          9,
          10,
          11,
          12,
          13,
          14,
          15
        ],
        "supports_audio": true
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": true,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.kling_v3_pro",
      "object": "model",
      "capability": "video_generation",
      "name": "Kling V3 Pro",
      "description": "Kling V3 pro — the higher-quality V3 tier. Text or image to video, 3-15s, generated audio, end frame on image-to-video, multi-shot prompts.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          3,
          4,
          5,
          6,
          7,
          8,
          9,
          10,
          11,
          12,
          13,
          14,
          15
        ],
        "supports_audio": true
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": true,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.kling_v3_turbo",
      "object": "model",
      "capability": "video_generation",
      "name": "Kling V3 Turbo Standard",
      "description": "Kling 3.0 Turbo standard (720p) — the fast, cheaper Kling V3 tier. Text or image to video, 3–15s, multi-shot prompts. No end frame, no elements, no audio.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          3,
          4,
          5,
          6,
          7,
          8,
          9,
          10,
          11,
          12,
          13,
          14,
          15
        ],
        "supports_audio": false
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.kling_v3_turbo_pro",
      "object": "model",
      "capability": "video_generation",
      "name": "Kling V3 Turbo Pro",
      "description": "Kling 3.0 Turbo pro (1080p) — fast Kling V3 at the standard-tier price. Text or image to video, 3–15s, multi-shot prompts. No end frame, no elements, no audio.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          3,
          4,
          5,
          6,
          7,
          8,
          9,
          10,
          11,
          12,
          13,
          14,
          15
        ],
        "supports_audio": false
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.kling_v3_motion_control",
      "object": "model",
      "capability": "video_generation",
      "name": "Kling V3 Motion Control",
      "description": "Transfer motion from a reference video onto a character image. Requires both image_url (character) and source_video_url (motion reference).",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          5
        ]
      },
      "capabilities": {
        "text_to_output": false,
        "image_to_output": false,
        "reference_images": false,
        "source_video": false,
        "motion_control": true,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.kling_v3_pro_motion_control",
      "object": "model",
      "capability": "video_generation",
      "name": "Kling V3 Pro Motion Control",
      "description": "Pro-tier Kling V3 motion-control — higher quality character animation. Requires both image_url and source_video_url.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          5
        ]
      },
      "capabilities": {
        "text_to_output": false,
        "image_to_output": false,
        "reference_images": false,
        "source_video": false,
        "motion_control": true,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.kling_v3_4k",
      "object": "model",
      "capability": "video_generation",
      "name": "Kling V3 4K",
      "description": "Kling V3 at 4K resolution — the delivery tier. Text or image to video, 3-15s, generated audio, end frame on image-to-video.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          3,
          4,
          5,
          6,
          7,
          8,
          9,
          10,
          11,
          12,
          13,
          14,
          15
        ],
        "resolution": "4k",
        "supports_audio": true
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": true,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.kling_v26",
      "object": "model",
      "capability": "video_generation",
      "name": "Kling 2.6",
      "description": "Kling 2.6 — text/image to video. Kling 2.6 has a single generation tier; this alias resolves to the same endpoints as video.kling_v26_pro.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          5,
          10
        ]
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": true,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.kling_v26_pro",
      "object": "model",
      "capability": "video_generation",
      "name": "Kling 2.6 Pro",
      "description": "Kling 2.6 pro tier — higher quality.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          5,
          10
        ]
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": true,
        "first_last_frame": false,
        "end_frame": true,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.kling_v26_motion_control",
      "object": "model",
      "capability": "video_generation",
      "name": "Kling 2.6 Motion Control",
      "description": "Motion-control variant of Kling 2.6 (legacy). Requires image_url + source_video_url.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          5
        ]
      },
      "capabilities": {
        "text_to_output": false,
        "image_to_output": false,
        "reference_images": false,
        "source_video": false,
        "motion_control": true,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.kling_v25_turbo",
      "object": "model",
      "capability": "video_generation",
      "name": "Kling 2.5 Turbo Pro",
      "description": "Faster Kling variant (legacy but reliable).",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          5,
          10
        ]
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.kling_v21_master",
      "object": "model",
      "capability": "video_generation",
      "name": "Kling 2.1 Master",
      "description": "Kling 2.1 Master (legacy) — enhanced quality.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          5,
          10
        ]
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.hailuo_03_max_multi_angle",
      "object": "model",
      "capability": "video_generation",
      "name": "H3 Max Multi Angle",
      "description": "H3 Max image-to-video on an explicit camera path: hand it ONE image and a `camera_path` preset (orbit, half_orbit, dolly_in, crane_up, top_down, spiral, hero_reveal, …) or 2-12 `camera_trajectory` keyframes of azimuth / elevation / distance, and it renders 5-15s of that move with the scene held rigid — product turnarounds, parallax reveals, virtual dolly shots. 480p/768p/1080p at the H3 Max rate (1080p bills 2x the 768p rate). Image-to-video ONLY: no text-to-video, no end frame, no references.",
      "status": "available",
      "is_default": false,
      "limits": {
        "duration_seconds": [
          5,
          6,
          7,
          8,
          9,
          10,
          11,
          12,
          13,
          14,
          15
        ],
        "resolutions": [
          "480p",
          "768p",
          "1080p"
        ],
        "camera_paths": [
          "orbit",
          "orbit_reverse",
          "half_orbit",
          "quarter_orbit",
          "dolly_in",
          "dolly_out",
          "crane_up",
          "low_angle",
          "top_down",
          "arc_push",
          "spiral",
          "hero_reveal"
        ],
        "camera_keyframes": {
          "min": 2,
          "max": 12
        }
      },
      "capabilities": {
        "text_to_output": false,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": true,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.hailuo_03_max_styles",
      "object": "model",
      "capability": "video_generation",
      "name": "H3 Max Styles",
      "description": "H3 Max with a baked-in look: pick ONE `video_style` — `vhs` (VHS), `retro_toon_70s` (Retro Toon 70s), `low_poly` (Low Poly), `hand_drawn` (Hand Drawn), `16bit_pixel` (16-bit Pixel) — and it renders your prompt in that style, 5-15s at a fixed 768p with audio, one flat per-second rate for every style (resolution does not scale it). Text-to-video, or image-to-video from an optional `image_url` first frame (its ratio then sets the canvas and `aspect_ratio` is ignored). `video_style` is REQUIRED; `damage_level` (light / medium / heavy, default medium) is the VHS tape wear and is accepted on `vhs` only. A prompt is required — the style is applied to the scene you describe. No end frame, no references, no trained models, no resolution choice.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "21:9",
          "16:9",
          "4:3",
          "1:1",
          "3:4",
          "9:16"
        ],
        "duration_seconds": [
          5,
          6,
          7,
          8,
          9,
          10,
          11,
          12,
          13,
          14,
          15
        ],
        "resolutions": [
          "768p"
        ],
        "video_styles": [
          "vhs",
          "retro_toon_70s",
          "low_poly",
          "hand_drawn",
          "16bit_pixel"
        ],
        "damage_levels": [
          "light",
          "medium",
          "heavy"
        ],
        "damage_level_styles": [
          "vhs"
        ],
        "prompt_max_length": 49800
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": true,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.hailuo_03_max_extend",
      "object": "model",
      "capability": "video_generation",
      "name": "H3 Max Extend",
      "description": "H3 Max Extend — continues an existing clip from a prompt. Pass `source_video_url` (1.625-60s, ≤50 MB) and a prompt describing what happens next; `duration` (5-15s) is the length of the NEW footage. `extend_output`: `extended` (default — the source plus the new footage as one clip) or `continuation` (the new footage only). `aspect_ratio` defaults to `auto` (follow the source). 480p/768p/1080p/2k on the H3 Max per-second ladder (1080p 2x, 2k 4x the 768p rate) plus a small source-clip token surcharge. No first frame, references or text-only mode.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "auto",
          "21:9",
          "16:9",
          "4:3",
          "1:1",
          "3:4",
          "9:16"
        ],
        "duration_seconds": [
          5,
          6,
          7,
          8,
          9,
          10,
          11,
          12,
          13,
          14,
          15
        ],
        "resolutions": [
          "480p",
          "768p",
          "1080p",
          "2k"
        ],
        "extend_outputs": [
          "extended",
          "continuation"
        ],
        "source_video_min_seconds": 1.625,
        "source_video_max_seconds": 60,
        "source_video_max_mb": 50,
        "prompt_max_length": 50000
      },
      "capabilities": {
        "text_to_output": false,
        "image_to_output": false,
        "reference_images": false,
        "source_video": true,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.hailuo_03_max_3d",
      "object": "model",
      "capability": "video_generation",
      "name": "H3 Max 3D to Video",
      "description": "H3 Max 3D to Video — turns a 3D render (Blender, previs, greybox) into a photoreal clip that keeps its camera, timing, trajectories and object count. Pass `source_video_url` (≤15s, strict); the output matches its length and framing, so there is NO `duration` or `aspect_ratio` control. `prompt` is OPTIONAL scene intent (≤2000 chars). Optional `reference_image_urls` (≤8) set the look of every shot; without them the model generates up to `max_generated_reference_images` (1-8, default 2) appearance references itself. 480p/768p/1080p. Billed per request: a flat planning fee + the H3 Max per-second rate on the source length (5s minimum) + each generated reference + a source-clip token surcharge — call POST /v1/models/estimate-cost with the source_video_url for the exact number.",
      "status": "available",
      "is_default": false,
      "limits": {
        "resolutions": [
          "480p",
          "768p",
          "1080p"
        ],
        "max_reference_images": 8,
        "max_generated_reference_images": {
          "min": 1,
          "max": 8,
          "default": 2
        },
        "source_video_max_seconds": 15,
        "prompt_max_length": 2000
      },
      "capabilities": {
        "text_to_output": false,
        "image_to_output": false,
        "reference_images": false,
        "source_video": true,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": true,
        "draft_enhance": false
      }
    },
    {
      "id": "video.hailuo_03_max_insert",
      "object": "model",
      "capability": "video_generation",
      "name": "H3 Max Insert",
      "description": "H3 Max Insert — cuts a NEW scene into an existing clip, then hands back to the original footage. Pass `source_video_url` (≤60s, ≤50 MB) plus `insert_start_time` (where the new scene begins, ≥1.625s) and `insert_resume_time` (where the original picks up again: later than the start, within the first 60s and before the clip ends — leave some footage after it for the transition), both REQUIRED, in seconds into `source_video_url` as supplied (the source is never trimmed first). Guide the new scene with `prompt` and/or up to 9 `reference_image_urls` and 3 `reference_video_urls` (15s combined) — at least one is required. Returns the WHOLE edited clip: the lead-in up to the start, the new scene, then the source from the resume point on. `duration` (5-13s) is the length of the NEW scene only — the only seconds billed — whatever span it replaces; reference clips add a small token surcharge on their measured (trimmed) length. `color_match` (default true) grades the new scene to the source. 480p or 768p (480p bills 5/6 of the 768p rate). No aspect_ratio (the output follows the source), no audio toggle, no first frame, no reference audio.",
      "status": "available",
      "is_default": false,
      "limits": {
        "duration_seconds": [
          5,
          6,
          7,
          8,
          9,
          10,
          11,
          12,
          13
        ],
        "resolutions": [
          "480p",
          "768p"
        ],
        "max_reference_images": 9,
        "max_reference_videos": 3,
        "max_reference_audio": 0,
        "max_reference_video_seconds": 15,
        "max_reference_video_seconds_total": 15,
        "insert_min_start_seconds": 1.625,
        "insert_max_time_seconds": 60,
        "source_video_max_seconds": 60,
        "source_video_max_mb": 50,
        "prompt_max_length": 50000
      },
      "capabilities": {
        "text_to_output": false,
        "image_to_output": false,
        "reference_images": true,
        "source_video": true,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": true,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": true,
        "draft_enhance": false
      }
    },
    {
      "id": "video.hailuo_03_max_recast",
      "object": "model",
      "capability": "video_generation",
      "name": "H3 Max Recast",
      "description": "H3 Max Recast — recasts the people in a video from up to 4 reference photos, keeping the motion, camera, cuts and sound. Pass `source_video_url` (5-30s, strict; no single shot longer than 15s) and `reference_image_urls` (1-4, one photo per new person — photo 1 replaces the leftmost main person, photo 2 the next, and so on), both REQUIRED. `prompt` is OPTIONAL (≤2000 chars): say who becomes whom, or what else to keep or change. The output keeps the source's length and framing, so there is NO `duration` or `aspect_ratio`. 768p or 1080p (default 1080p; 768p bills 2/3 of the 1080p rate). Billed per second of the SOURCE as measured server-side (rounded up, 5s minimum); the photos are free, and a source that cannot be measured is refused rather than guessed — call POST /v1/models/estimate-cost with the source_video_url for the exact number. No reference clips or audio.",
      "status": "available",
      "is_default": false,
      "limits": {
        "resolutions": [
          "768p",
          "1080p"
        ],
        "min_reference_images": 1,
        "max_reference_images": 4,
        "source_video_min_seconds": 5,
        "source_video_max_seconds": 30,
        "source_video_max_shot_seconds": 15,
        "prompt_max_length": 2000
      },
      "capabilities": {
        "text_to_output": false,
        "image_to_output": false,
        "reference_images": true,
        "source_video": true,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": true,
        "draft_enhance": false
      }
    },
    {
      "id": "video.hailuo_03_max_turbo",
      "object": "model",
      "capability": "video_generation",
      "name": "H3 Max Turbo",
      "description": "The Turbo tier of H3 Max — the same 480p/768p/1080p output from text or a first frame (with optional last-frame keyframe), at half the H3 Max price per second (1080p bills 2x the 768p rate). Text-to-video and image-to-video only: there is no reference mode, so use video.hailuo_03_max for references and video.hailuo_03 for 2K.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "21:9",
          "16:9",
          "4:3",
          "1:1",
          "3:4",
          "9:16"
        ],
        "duration_seconds": [
          5,
          6,
          7,
          8,
          9,
          10,
          11,
          12,
          13,
          14,
          15
        ],
        "resolutions": [
          "480p",
          "768p",
          "1080p"
        ]
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": true,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.hailuo_03_max",
      "object": "model",
      "capability": "video_generation",
      "name": "H3 Max",
      "description": "A post-trained MiniMax H3 variant — stronger prompt adherence and higher throughput, from text, a first frame (with optional last-frame keyframe), or reference images/videos/audio. Renders 480p/768p/1080p (1080p bills 2x the 768p rate); use video.hailuo_03 for 2K.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "21:9",
          "16:9",
          "4:3",
          "1:1",
          "3:4",
          "9:16"
        ],
        "duration_seconds": [
          5,
          6,
          7,
          8,
          9,
          10,
          11,
          12,
          13,
          14,
          15
        ],
        "resolutions": [
          "480p",
          "768p",
          "1080p"
        ],
        "reference_image_urls": 9,
        "reference_video_urls": 3,
        "reference_audio_urls": 3,
        "reference_media_seconds": [
          2,
          15
        ]
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": true,
        "reference_media": true,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.hailuo_03",
      "object": "model",
      "capability": "video_generation",
      "name": "Hailuo 03",
      "description": "MiniMax Hailuo 03 — 2K video from text, a first frame (with optional last-frame keyframe), or reference images/videos/audio. The only model that runs your TRAINED MODELS: pass `loras: [{ id, scale? }]` (up to 3, from POST /v1/loras) and the clip renders through the matching Hailuo 03 LoRA endpoint.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "21:9",
          "16:9",
          "4:3",
          "1:1",
          "3:4",
          "9:16"
        ],
        "duration_seconds": [
          5,
          6,
          7,
          8,
          9,
          10,
          11,
          12,
          13,
          14,
          15
        ],
        "resolutions": [
          "768p",
          "2k"
        ],
        "reference_image_urls": 9,
        "reference_video_urls": 3,
        "reference_audio_urls": 3,
        "reference_media_seconds": [
          2,
          15
        ],
        "loras": 3
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": true,
        "reference_media": true,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.hailuo_23",
      "object": "model",
      "capability": "video_generation",
      "name": "Hailuo 2.3",
      "description": "MiniMax Hailuo 2.3 — dynamic video generation.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          6,
          10
        ]
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.hailuo_23_pro",
      "object": "model",
      "capability": "video_generation",
      "name": "Hailuo 2.3 Pro",
      "description": "MiniMax Hailuo 2.3 Pro tier.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          6,
          10
        ]
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.hailuo_02",
      "object": "model",
      "capability": "video_generation",
      "name": "Hailuo 02",
      "description": "MiniMax Hailuo 02 (legacy) — image-to-video standard tier.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          6,
          10
        ]
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": true,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.hailuo_02_fast",
      "object": "model",
      "capability": "video_generation",
      "name": "Hailuo 02 Fast",
      "description": "Faster Hailuo 02 image-to-video variant.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          6,
          10
        ]
      },
      "capabilities": {
        "text_to_output": false,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.hailuo_23_fast",
      "object": "model",
      "capability": "video_generation",
      "name": "Hailuo 2.3 Fast",
      "description": "Faster Hailuo 2.3 image-to-video standard tier.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          6,
          10
        ]
      },
      "capabilities": {
        "text_to_output": false,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.hailuo_23_fast_pro",
      "object": "model",
      "capability": "video_generation",
      "name": "Hailuo 2.3 Fast Pro",
      "description": "Faster Hailuo 2.3 pro-tier image-to-video.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          6,
          10
        ]
      },
      "capabilities": {
        "text_to_output": false,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.luma_dream_machine",
      "object": "model",
      "capability": "video_generation",
      "name": "Luma Dream Machine",
      "description": "Luma Dream Machine — fluid motion, distinctive style.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          5,
          9
        ]
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.wan_v22",
      "object": "model",
      "capability": "video_generation",
      "name": "WAN 2.2",
      "description": "WAN 2.2 — strong character animation.",
      "status": "preview",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          5,
          8
        ]
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.wan_25_preview",
      "object": "model",
      "capability": "video_generation",
      "name": "WAN 2.5 Preview",
      "description": "WAN 2.5 preview — newer than 2.2, may change without notice.",
      "status": "preview",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          5,
          8
        ]
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.wan_animate_replace",
      "object": "model",
      "capability": "video_generation",
      "name": "WAN Animate (Replace)",
      "description": "WAN animate-replace — swap subject in source video using a reference image. Requires image_url + source_video_url.",
      "status": "preview",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          5
        ]
      },
      "capabilities": {
        "text_to_output": false,
        "image_to_output": false,
        "reference_images": false,
        "source_video": false,
        "motion_control": true,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.wan_animate_move",
      "object": "model",
      "capability": "video_generation",
      "name": "WAN Animate (Move)",
      "description": "WAN animate-move — animate static images with natural motion from a reference video. Requires image_url + source_video_url.",
      "status": "preview",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          5
        ]
      },
      "capabilities": {
        "text_to_output": false,
        "image_to_output": false,
        "reference_images": false,
        "source_video": false,
        "motion_control": true,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.seedance_2_0",
      "object": "model",
      "capability": "video_generation",
      "name": "Seedance 2.0",
      "description": "ByteDance Seedance 2.0 — text/image/reference to video. Supports up to 4k.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "auto",
          "21:9",
          "16:9",
          "4:3",
          "1:1",
          "3:4",
          "9:16"
        ],
        "duration_seconds": [
          4,
          5,
          6,
          7,
          8,
          9,
          10,
          11,
          12,
          13,
          14,
          15
        ],
        "resolutions": [
          "480p",
          "720p",
          "1080p",
          "4k"
        ],
        "supports_audio": true
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": true,
        "reference_media": true,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": true,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.seedance_2_0_fast",
      "object": "model",
      "capability": "video_generation",
      "name": "Seedance 2.0 Fast",
      "description": "Faster, cheaper Seedance 2.0 variant. Caps at 720p.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "auto",
          "21:9",
          "16:9",
          "4:3",
          "1:1",
          "3:4",
          "9:16"
        ],
        "duration_seconds": [
          4,
          5,
          6,
          7,
          8,
          9,
          10,
          11,
          12,
          13,
          14,
          15
        ],
        "resolutions": [
          "480p",
          "720p"
        ],
        "supports_audio": true
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": true,
        "reference_media": true,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": true,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.seedance_2_0_mini",
      "object": "model",
      "capability": "video_generation",
      "name": "Seedance 2.0 Mini",
      "description": "Smallest, cheapest Seedance 2.0 variant. Caps at 720p; no bitrate_mode.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "auto",
          "21:9",
          "16:9",
          "4:3",
          "1:1",
          "3:4",
          "9:16"
        ],
        "duration_seconds": [
          4,
          5,
          6,
          7,
          8,
          9,
          10,
          11,
          12,
          13,
          14,
          15
        ],
        "resolutions": [
          "480p",
          "720p"
        ],
        "supports_audio": true
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": true,
        "reference_media": true,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.seedance_2_5",
      "object": "model",
      "capability": "video_generation",
      "name": "Seedance 2.5",
      "description": "ByteDance Seedance 2.5 — text/image/reference to video, and the engine behind Genfire Genjudo. 4-30s clips with native audio; reference-to-video takes up to 30 images, 10 videos, and 10 audio clips and a `task` of reference (motion transfer), editing (video edit) or extension. 480p/720p/1080p (no 4k) and no speed tiers. `draft: true` renders a 480p preview at the 480p price that POST /v1/videos/generations/{id}/final turns into the native 1080p final of the same shot within 7 days.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "auto",
          "21:9",
          "16:9",
          "4:3",
          "1:1",
          "3:4",
          "9:16"
        ],
        "duration_seconds": [
          4,
          5,
          6,
          7,
          8,
          9,
          10,
          11,
          12,
          13,
          14,
          15,
          16,
          17,
          18,
          19,
          20,
          21,
          22,
          23,
          24,
          25,
          26,
          27,
          28,
          29,
          30
        ],
        "resolutions": [
          "480p",
          "720p",
          "1080p"
        ],
        "supports_audio": true,
        "max_reference_images": 30,
        "max_reference_videos": 10,
        "max_reference_audio": 10,
        "min_reference_video_seconds": 2,
        "max_reference_video_seconds": 30,
        "max_reference_video_seconds_total": 30,
        "video_tasks": [
          "reference",
          "editing",
          "extension"
        ]
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": true,
        "reference_media": true,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": true,
        "bitrate_mode": false,
        "draft": true,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.wan_3",
      "object": "model",
      "capability": "video_generation",
      "name": "Wan 3.0",
      "description": "Alibaba Wan 3.0 — text/image/reference to video. 2-30s clips with native synchronized audio; reference-to-video takes up to 10 images, 5 videos, and 5 audio clips. 480p/720p/1080p (default 1080p, no 4k) and no speed tiers.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "adaptive",
          "16:9",
          "4:3",
          "1:1",
          "3:4",
          "9:16"
        ],
        "duration_seconds": [
          2,
          3,
          4,
          5,
          6,
          7,
          8,
          9,
          10,
          11,
          12,
          13,
          14,
          15,
          16,
          17,
          18,
          19,
          20,
          21,
          22,
          23,
          24,
          25,
          26,
          27,
          28,
          29,
          30
        ],
        "resolutions": [
          "480p",
          "720p",
          "1080p"
        ],
        "supports_audio": true,
        "max_reference_images": 10,
        "max_reference_videos": 5,
        "max_reference_audio": 5
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": true,
        "reference_media": true,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.wan_3_prime",
      "object": "model",
      "capability": "video_generation",
      "name": "Wan 3.0 Prime",
      "description": "Alibaba Wan 3.0 Prime — the faster, higher-fidelity Wan 3.0 tier at 1.4x the price. Identical inputs: text/image/reference to video, 2-30s, native audio, up to 10 image + 5 video + 5 audio references, 480p/720p/1080p.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "adaptive",
          "16:9",
          "4:3",
          "1:1",
          "3:4",
          "9:16"
        ],
        "duration_seconds": [
          2,
          3,
          4,
          5,
          6,
          7,
          8,
          9,
          10,
          11,
          12,
          13,
          14,
          15,
          16,
          17,
          18,
          19,
          20,
          21,
          22,
          23,
          24,
          25,
          26,
          27,
          28,
          29,
          30
        ],
        "resolutions": [
          "480p",
          "720p",
          "1080p"
        ],
        "supports_audio": true,
        "max_reference_images": 10,
        "max_reference_videos": 5,
        "max_reference_audio": 5
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": true,
        "reference_media": true,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.grok_video",
      "object": "model",
      "capability": "video_generation",
      "name": "Grok Imagine Video",
      "description": "xAI Grok Imagine — high-fidelity text/image to video.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "4:3",
          "3:2",
          "1:1",
          "2:3",
          "3:4",
          "9:16"
        ],
        "duration_seconds": [
          1,
          2,
          3,
          4,
          5,
          6,
          7,
          8,
          9,
          10
        ],
        "resolutions": [
          "480p",
          "720p"
        ]
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.grok_video_v15",
      "object": "model",
      "capability": "video_generation",
      "name": "Grok Imagine Video v1.5",
      "description": "xAI Grok Imagine v1.5 — text-to-video, image-to-video, or reference-to-video from up to 7 reference images.",
      "status": "available",
      "is_default": false,
      "limits": {
        "duration_seconds": [
          1,
          2,
          3,
          4,
          5,
          6,
          7,
          8,
          9,
          10,
          11,
          12,
          13,
          14,
          15
        ],
        "resolutions": [
          "480p",
          "720p",
          "1080p"
        ],
        "aspect_ratios": [
          "16:9",
          "4:3",
          "3:2",
          "1:1",
          "2:3",
          "3:4",
          "9:16"
        ]
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.grok_video_v15_lite",
      "object": "model",
      "capability": "video_generation",
      "name": "Grok Imagine v1.5 Lite",
      "description": "xAI Grok Imagine v1.5 Lite — the budget Grok v1.5: text-to-video or image-to-video, any whole second 1-15 (default 5), 480p / 720p / 1080p (default 720p). No reference images. aspect_ratio applies to text-to-video only; image-to-video follows the start image. 1080p bills 7x the 480p rate.",
      "status": "available",
      "is_default": false,
      "limits": {
        "duration_seconds": [
          1,
          2,
          3,
          4,
          5,
          6,
          7,
          8,
          9,
          10,
          11,
          12,
          13,
          14,
          15
        ],
        "resolutions": [
          "480p",
          "720p",
          "1080p"
        ],
        "aspect_ratios": [
          "16:9",
          "4:3",
          "3:2",
          "1:1",
          "2:3",
          "3:4",
          "9:16"
        ],
        "max_prompt_length": 4096
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.grok_video_edit",
      "object": "model",
      "capability": "video_generation",
      "name": "Grok Imagine Edit",
      "description": "Edit existing videos with text prompts. Requires source_video_url.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          4,
          5,
          8,
          10
        ]
      },
      "capabilities": {
        "text_to_output": false,
        "image_to_output": false,
        "reference_images": false,
        "source_video": true,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.grok_video_extend",
      "object": "model",
      "capability": "video_generation",
      "name": "Grok Imagine Extend",
      "description": "Extend an existing video by 2–10s. Requires source_video_url.",
      "status": "available",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1"
        ],
        "duration_seconds": [
          2,
          5,
          10
        ]
      },
      "capabilities": {
        "text_to_output": false,
        "image_to_output": false,
        "reference_images": false,
        "source_video": true,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "video.happy_horse",
      "object": "model",
      "capability": "video_generation",
      "name": "Happy Horse v1.1",
      "description": "Alibaba Happy Horse v1.1 — text/image/reference-to-video, plus video-edit.",
      "status": "preview",
      "is_default": false,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16",
          "1:1",
          "4:3",
          "3:4",
          "21:9",
          "9:21",
          "5:4",
          "4:5"
        ],
        "duration_seconds": [
          3,
          4,
          5,
          6,
          7,
          8,
          9,
          10,
          11,
          12,
          13,
          14,
          15
        ]
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": true,
        "source_video": true,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "speech.elevenlabs_turbo_v2_5",
      "object": "model",
      "capability": "speech_generation",
      "name": "ElevenLabs Turbo v2.5",
      "description": "Legacy low-latency TTS — deprecated upstream in favour of Flash v2.5 (same price, lower latency). Still works when pinned.",
      "status": "available",
      "is_default": false,
      "limits": {
        "max_characters": 40000
      }
    },
    {
      "id": "speech.elevenlabs_flash_v2_5",
      "object": "model",
      "capability": "speech_generation",
      "name": "ElevenLabs Flash v2.5",
      "description": "Fastest ElevenLabs TTS (~75ms), lowest cost, 32 languages, up to 40,000 characters. Accepts speed, language_code, seed, previous_text/next_text and apply_text_normalization. A request that names no model and is longer than the default (speech.elevenlabs_v4_turbo) accepts runs here.",
      "status": "available",
      "is_default": false,
      "limits": {
        "max_characters": 40000
      }
    },
    {
      "id": "speech.elevenlabs_dialogue_v3",
      "object": "model",
      "capability": "speech_generation",
      "name": "ElevenLabs Text to Dialogue (v3)",
      "description": "Previous-generation Text to Dialogue on Eleven v3 (speech.elevenlabs_dialogue_v4 is the default). Multi-speaker conversation rendered as ONE audio file with matched prosody between lines. Send `dialogue: [{ text, voice_id }]` (up to 10 distinct ElevenLabs voices, 2000 characters total) instead of text + voice_id; lines accept v3 audio tags such as [whispers] or [laughs]. Optional stability (0 | 0.5 | 1), seed, language_code, with_timestamps (returns per-word timings + per-line voice segments). Billed like TTS on total characters.",
      "status": "available",
      "is_default": false,
      "limits": {
        "max_characters": 2000,
        "max_voices": 10
      }
    },
    {
      "id": "speech.elevenlabs_dialogue_v4",
      "object": "model",
      "capability": "speech_generation",
      "name": "ElevenLabs Text to Dialogue (v4)",
      "description": "Multi-speaker conversation on Eleven v4 (ElevenLabs' recommended dialogue model) — the default dialogue model: `dialogue` sent with no model, or with any alias that is not dialogue-capable, runs here. Rendered as ONE audio file with matched prosody between lines. Send `dialogue: [{ text, voice_id }]` (up to 10 distinct ElevenLabs voices, 2000 characters total) instead of text + voice_id; lines accept audio tags such as [whispers] or [laughs]. Optional stability (any value 0–1), seed, language_code, with_timestamps (returns per-word timings + per-line voice segments). Billed like TTS on total characters.",
      "status": "available",
      "is_default": false,
      "limits": {
        "max_characters": 2000,
        "max_voices": 10
      }
    },
    {
      "id": "speech.elevenlabs_v4",
      "object": "model",
      "capability": "speech_generation",
      "name": "ElevenLabs v4",
      "description": "Most expressive ElevenLabs voice model: audio tags such as [whispers] or [laughs], 85 languages, up to 10,000 characters per request. Voice settings are stability + similarity only — style, speed and speaker boost are ignored. Accepts language_code and seed.",
      "status": "available",
      "is_default": false,
      "limits": {
        "max_characters": 10000
      }
    },
    {
      "id": "speech.elevenlabs_v4_turbo",
      "object": "model",
      "capability": "speech_generation",
      "name": "ElevenLabs v4 Turbo",
      "description": "Default speech model. Eleven v4 expressiveness (audio tags, 85 languages) at ~100 ms median inference latency and half v4's cost; up to 10,000 characters per request (a request that names no model and is longer runs on speech.elevenlabs_flash_v2_5). Voice settings are stability + similarity only — style, speed and speaker boost are ignored; sending `speed` without a model uses speech.elevenlabs_flash_v2_5 automatically, as does a no-model request on one of your account's cloned ElevenLabs voices. `dialogue` requests sent with this alias run on Eleven v4 dialogue (speech.elevenlabs_dialogue_v4).",
      "status": "available",
      "is_default": true,
      "limits": {
        "max_characters": 10000
      }
    },
    {
      "id": "speech.elevenlabs_v3",
      "object": "model",
      "capability": "speech_generation",
      "name": "ElevenLabs v3",
      "description": "Previous-generation expressive voice model (audio tags such as [whispers]); up to 5,000 characters. Superseded by speech.elevenlabs_v4.",
      "status": "available",
      "is_default": false,
      "limits": {
        "max_characters": 5000
      }
    },
    {
      "id": "speech.elevenlabs_multilingual_v2",
      "object": "model",
      "capability": "speech_generation",
      "name": "ElevenLabs Multilingual v2",
      "description": "29 languages, high fidelity.",
      "status": "available",
      "is_default": false,
      "limits": {
        "max_characters": 10000
      }
    },
    {
      "id": "speech.seed_audio_1_0",
      "object": "model",
      "capability": "speech_generation",
      "name": "Seed Audio 1.0",
      "description": "ByteDance Seed Audio 1.0 — general text-to-audio: natural multilingual TTS plus full audio scenes (dialogue, ambience, radio dramas) from a prompt. voice_id is OPTIONAL and takes a Seed preset name (e.g. `vivi_mixed_en_zh_ja_es_id`, `mindy_en_es_id_pt_zh`, `kian_en_zh`, `sophie_en_zh`, `magnus_en_zh`) — NOT an ElevenLabs/cloned voice id; omit it to let the model narrate. Accepts up to 3 reference audio clips via audio_urls (reference them in the prompt as @Audio1–@Audio3) OR one reference image via image_url, plus speed/volume/pitch, output_format (wav|mp3|pcm|ogg_opus) and sample_rate. Billed per second (billed seconds ≈ ceil(text length / 15)).",
      "status": "available",
      "is_default": false,
      "limits": {
        "max_characters": 2048,
        "max_reference_audio": 3
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "music.elevenlabs_music_v1",
      "object": "model",
      "capability": "music_generation",
      "name": "ElevenLabs Music v1",
      "description": "Legacy prompt-based music generation up to 10 minutes — deprecated upstream (ElevenLabs marks music_v1 deprecated; music.elevenlabs_music_v2_5 is the default). Also accepts a `composition_plan` (MusicPrompt: positive/negative_global_styles + sections[] with lyrics lines) instead of a prompt; `respect_sections_durations` controls how strictly section durations are enforced.",
      "status": "available",
      "is_default": false,
      "limits": {
        "max_duration_seconds": 600
      }
    },
    {
      "id": "music.elevenlabs_music_v2",
      "object": "model",
      "capability": "music_generation",
      "name": "ElevenLabs Music v2",
      "description": "ElevenLabs music_v2 — the previous-generation chunk-plan model, still available when pinned. Music generation up to 10 minutes from a prompt, or a chunk-based `composition_plan` (chunks[] with text incl. [Section]/{direction} markup, duration_ms, positive/negative_styles, optional per-chunk audio conditioning refs + `seed`). Pass `store_for_inpainting` to get a `song_id` back, then reference `{ song_id, range }` chunks in a later plan to regenerate only part of the song. Also scores a `video_url` (video-to-music) when one is supplied instead of a prompt. music.elevenlabs_music_v2_5 (the default) takes the same fields.",
      "status": "available",
      "is_default": false,
      "limits": {
        "max_duration_seconds": 600
      }
    },
    {
      "id": "music.elevenlabs_music_v2_5",
      "object": "model",
      "capability": "music_generation",
      "name": "ElevenLabs Music v2.5",
      "description": "ElevenLabs music_v2_5 (default) — the newest ElevenLabs music model (released 2026-09). A prompt up to 10 minutes, or a chunk-based `composition_plan` (chunks[] with text incl. [Section]/{direction} markup, duration_ms, positive/negative_styles, optional per-chunk audio conditioning refs + `seed`), `force_instrumental`, word timestamps and inpainting (`store_for_inpainting` → `song_id`, then `{ song_id, range }` chunks). Also scores a `video_url` (video-to-music) when one is supplied instead of a prompt. Same fields as music.elevenlabs_music_v2.",
      "status": "available",
      "is_default": true,
      "limits": {
        "max_duration_seconds": 600
      }
    },
    {
      "id": "music.lyria3_pro",
      "object": "model",
      "capability": "music_generation",
      "name": "Lyria 3 Pro",
      "description": "Google Lyria 3 Pro — full structured songs up to 3 minutes with vocals, lyrics, and multi-language support. Accepts an optional image_url for inspiration (no negative prompting). Flat per-generation fee (duration is not configurable).",
      "status": "available",
      "is_default": false,
      "limits": {
        "max_duration_seconds": 180,
        "prompt_max_length": 5000
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "music.lyria_3_5",
      "object": "model",
      "capability": "music_generation",
      "name": "Lyria 3.5",
      "description": "Google Lyria 3.5 — full-length songs with vocals; writes and sings its own lyrics. Steer length and structure from the prompt (timestamps or a duration hint) — there is no duration parameter. Accepts an optional image_url for inspiration (no negative prompting). Flat per-generation fee whatever the length.",
      "status": "available",
      "is_default": false,
      "limits": {
        "prompt_max_length": 5000
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "music.minimax_music_3",
      "object": "model",
      "capability": "music_generation",
      "name": "MiniMax Music 3",
      "description": "MiniMax Music 3 — sings YOUR lyrics. `lyrics` is REQUIRED (structure tags like [verse]/[chorus] must each sit on their own line) alongside a `prompt` describing genre, BPM, key, vocals and arrangement. `duration_seconds` is an upper bound of 1-300 (default 60; the model may finish earlier) and billing is per second of that bound. Also accepts `num_inference_steps` (1-100, default 30), `guidance_scale` (0-20, default 1.7) and `seed`. Returns 44.1 kHz stereo WAV. No image input, no negative prompting, no composition_plan.",
      "status": "available",
      "is_default": false,
      "limits": {
        "max_duration_seconds": 300,
        "prompt_max_length": 5000
      },
      "capabilities": {
        "text_to_output": true,
        "image_to_output": false,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "sfx.elevenlabs_v1",
      "object": "model",
      "capability": "sound_effect_generation",
      "name": "ElevenLabs SFX",
      "description": "Sound effect generation up to 30s.",
      "status": "available",
      "is_default": true,
      "limits": {
        "max_duration_seconds": 30
      }
    },
    {
      "id": "transcription.whisper_v1",
      "object": "model",
      "capability": "transcription",
      "name": "OpenAI Whisper v1",
      "description": "Speech-to-text with word and segment timestamps. Accepts a direct audio/video URL or a YouTube URL. Async — returns a queued run; poll genfire_get_run for the transcript.",
      "status": "available",
      "is_default": true,
      "limits": {
        "max_file_mb": 500,
        "max_duration_seconds": 7200
      }
    },
    {
      "id": "transcription.elevenlabs_scribe_v2",
      "object": "model",
      "capability": "transcription",
      "name": "ElevenLabs Scribe v2",
      "description": "ElevenLabs Scribe v2 speech-to-text: 90+ languages, word timestamps, speaker diarization (up to 32 speakers, `speaker_id` on every word), audio-event tags, `keyterms` vocabulary biasing (up to 1000 terms), `entity_detection` / `entity_redaction` (pii, phi, pci, …), `no_verbatim` filler removal and `detect_speaker_roles`. Same inputs as Whisper (audio_url / video_url / youtube_url); segments are grouped by speaker.",
      "status": "available",
      "is_default": false,
      "limits": {
        "max_file_mb": 500,
        "max_duration_seconds": 7200,
        "max_speakers": 32,
        "max_keyterms": 1000
      }
    },
    {
      "id": "image_edit.bria_background_remove",
      "object": "model",
      "capability": "image_editing",
      "name": "BRIA Background Remove",
      "description": "Remove the background from an image. Returns a transparent PNG.",
      "status": "available",
      "is_default": true,
      "limits": {
        "input": "image_url"
      }
    },
    {
      "id": "video_upscale.topaz",
      "object": "model",
      "capability": "video_upscaling",
      "name": "Topaz Video Upscale",
      "description": "Upscale a video by 2x or 4x with Topaz (Proteus). Billed per second by output resolution. Async — returns a queued run; poll genfire_get_run for completion.",
      "status": "available",
      "is_default": true,
      "limits": {
        "scale_factors": [
          2,
          4
        ]
      }
    },
    {
      "id": "video_upscale.flux_video_upscale",
      "object": "model",
      "capability": "video_upscaling",
      "name": "Flux Video Upscale",
      "description": "Upscale a video 1.5x–3x with FLUX 3 super-resolution. Two modes: precise (source-faithful) and creative (adds detail, accepts an optional guiding prompt). Source must be MP4, at most 20s and 50MB. Billed per second of output by resolution and mode — materially pricier than Topaz. Async — returns a queued run; poll genfire_get_run for completion.",
      "status": "available",
      "is_default": false,
      "limits": {
        "scale_factors": [
          1.5,
          2,
          2.5,
          3
        ],
        "min_scale_factor": 1.5,
        "max_scale_factor": 3,
        "max_duration_seconds": 20,
        "max_file_mb": 50,
        "modes": [
          "precise",
          "creative"
        ]
      }
    },
    {
      "id": "image_upscale.topaz_upscale_image",
      "object": "model",
      "capability": "image_upscaling",
      "name": "Topaz Upscale",
      "description": "Upscale an image by 2x or 4x. Async — returns a queued run; poll genfire_get_run for completion.",
      "status": "available",
      "is_default": true,
      "limits": {
        "scale_factors": [
          2,
          4
        ]
      }
    },
    {
      "id": "lipsync.lipsync_2_pro",
      "object": "model",
      "capability": "lipsync_generation",
      "name": "Lipsync 2 Pro",
      "description": "High-quality lip-sync at 480p or 720p. Default.",
      "status": "available",
      "is_default": true,
      "limits": {
        "resolutions": [
          "480p",
          "720p"
        ]
      }
    },
    {
      "id": "lipsync.sync_lipsync_v2_pro",
      "object": "model",
      "capability": "lipsync_generation",
      "name": "Sync Lipsync v2 Pro",
      "description": "Alternative lip-sync engine.",
      "status": "available",
      "is_default": false,
      "limits": {
        "resolutions": [
          "480p",
          "720p"
        ]
      }
    },
    {
      "id": "lipsync.sync_lipsync_v3",
      "object": "model",
      "capability": "lipsync_generation",
      "name": "Sync Lipsync v3",
      "description": "Premium lip-sync engine — higher fidelity than v2 Pro.",
      "status": "available",
      "is_default": false,
      "limits": {
        "resolutions": [
          "480p",
          "720p"
        ]
      }
    },
    {
      "id": "lipsync.h3_max_lipsync",
      "object": "model",
      "capability": "lipsync_generation",
      "name": "H3 Max Lip Sync",
      "description": "Animate a PHOTO to speech audio (send image_url instead of video_url). 480P–2K. Audio must be 5–14.8 s; longer audio is clipped at 14.8 s. No prompt — it follows the audio.",
      "status": "available",
      "is_default": false,
      "limits": {
        "resolutions": [
          "480P",
          "768P",
          "1080P",
          "2K"
        ],
        "defaultResolution": "768P",
        "minAudioSeconds": 5,
        "maxAudioSeconds": 14.8,
        "imageAspectRatioRange": [
          0.4,
          2.5
        ]
      },
      "capabilities": {
        "text_to_output": false,
        "image_to_output": true,
        "reference_images": false,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "3d.meshy_v7",
      "object": "model",
      "capability": "model_3d_generation",
      "name": "Meshy v7",
      "description": "Convert one image (or 1–4 images of the same object from different angles) into a textured 3D model. Outputs GLB/FBX/OBJ/USDZ/BLEND/STL plus optional PBR maps, auto-rigging, and animation. Adds ultra_mode over v6 — higher-fidelity geometry with finer surface detail (single image only). Game-ready — GLB loads natively in three.js / model-viewer. Async — returns a queued run; poll genfire_get_run for the model URLs. Processing takes 5–10 minutes.",
      "status": "available",
      "is_default": true,
      "limits": {
        "max_images": 4,
        "output_formats": [
          "glb",
          "fbx",
          "obj",
          "usdz",
          "blend",
          "stl"
        ],
        "target_polycount": {
          "min": 100,
          "max": 300000,
          "default": 30000
        },
        "topology": [
          "quad",
          "triangle"
        ],
        "ultra_mode": "single image only"
      },
      "capabilities": {
        "text_to_output": false,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "3d.meshy_v6",
      "object": "model",
      "capability": "model_3d_generation",
      "name": "Meshy v6",
      "description": "Previous-generation Meshy. Same inputs and outputs as v7 minus ultra_mode — pin this only to reproduce earlier results. Converts one image (or 1–4 images of the same object from different angles) into a textured 3D model. Outputs GLB/FBX/OBJ/USDZ/BLEND/STL plus optional PBR maps, auto-rigging, and animation. Async — returns a queued run; poll genfire_get_run for the model URLs. Processing takes 5–10 minutes.",
      "status": "available",
      "is_default": false,
      "limits": {
        "max_images": 4,
        "output_formats": [
          "glb",
          "fbx",
          "obj",
          "usdz",
          "blend",
          "stl"
        ],
        "target_polycount": {
          "min": 100,
          "max": 300000,
          "default": 30000
        },
        "topology": [
          "quad",
          "triangle"
        ]
      },
      "capabilities": {
        "text_to_output": false,
        "image_to_output": true,
        "reference_images": true,
        "source_video": false,
        "motion_control": false,
        "first_last_frame": false,
        "end_frame": false,
        "reference_media": false,
        "camera_trajectory": false,
        "video_styles": false,
        "masked_inpaint": false,
        "video_task": false,
        "bitrate_mode": false,
        "draft": false,
        "keyframes": false,
        "video_to_video_reference": false,
        "draft_enhance": false
      }
    },
    {
      "id": "game.genfire_v1",
      "object": "model",
      "capability": "game_generation",
      "name": "Genfire Games",
      "description": "Describe a game and get a fully-playable browser game (self-contained HTML, Phaser/canvas/Three.js). The completed run carries a public play_url you can open or share. Iterate by passing game_id with a change prompt. Async — returns a queued run; the codegen runs on a worker (usually 1–3 min). Poll GET /runs/{id} until status is completed, then read output.play_url.",
      "status": "available",
      "is_default": true,
      "limits": {
        "output": "playable_html",
        "shareable": true
      }
    },
    {
      "id": "app.genfire_v1",
      "object": "model",
      "capability": "app_generation",
      "name": "Genfire App Builder",
      "description": "Describe an app and get a working full-stack single-file web app (UI + in-browser data layer, hosted live). The completed run carries a public live_url you can open or share. Iterate by passing app_id with a change prompt. Async — returns a queued run; codegen runs on a worker (usually 1–3 min). Poll GET /runs/{id} until status is completed, then read output.live_url.",
      "status": "available",
      "is_default": true,
      "limits": {
        "output": "live_web_app",
        "shareable": true
      }
    },
    {
      "id": "reel.faceless",
      "object": "model",
      "capability": "faceless_reel_generation",
      "name": "Faceless Reel",
      "description": "End-to-end vertical (9:16) short: LLM script → voiceover → style-locked images → music → captioned video. Driven by a niche preset + visual style; cost varies with duration and music — call POST /v1/faceless-reels/estimate-cost for a per-config estimate.",
      "status": "available",
      "is_default": true,
      "limits": {
        "aspect_ratios": [
          "9:16"
        ],
        "target_duration_sec": {
          "min": 10,
          "max": 120,
          "default": null
        },
        "music_sources": [
          "none",
          "preset",
          "ai",
          "library"
        ],
        "catalogs": [
          "presets",
          "styles",
          "music-presets",
          "caption-presets"
        ]
      }
    },
    {
      "id": "explainer.omni",
      "object": "model",
      "capability": "explainer_generation",
      "name": "Explainer",
      "description": "Long-form narrated explainer/documentary (20s–10min, 16:9 or 9:16): script → expressive voiceover → style-locked frames → Gemini Omni Flash video clips → composed film with optional captions. Pass `script` (structured beats) to author the entire creative contract with YOUR OWN model — narration, per-scene shot-specs, motion notes, render modes, reference routing, emphasis words and recurring cast — and Genfire only renders; or pass just `topic` and Genfire writes the script. Cost varies with duration — call POST /v1/explainers/estimate-cost first.",
      "status": "available",
      "is_default": true,
      "limits": {
        "aspect_ratios": [
          "16:9",
          "9:16"
        ],
        "target_duration_sec": {
          "min": 20,
          "max": 600,
          "default": 60
        },
        "music_sources": [
          "none",
          "preset",
          "ai",
          "library"
        ],
        "script_beats": {
          "min": 3,
          "max": 100
        },
        "reference_images": {
          "max": 8
        },
        "cast_members": {
          "max": 3
        },
        "caption_modes": [
          "full",
          "keywords"
        ],
        "caption_positions": [
          "top",
          "middle",
          "bottom"
        ],
        "catalogs": [
          "styles"
        ]
      }
    },
    {
      "id": "music-video.omni",
      "object": "model",
      "capability": "music_video_generation",
      "name": "Music Video",
      "description": "Auto-produced AI music video (9:16 or 16:9) from a song: song → beat/section analysis → style-locked anchor frame → per-scene MiniMax H3 Max Turbo clips cut to the music, with performance shots lip-synced to the song itself (H3 Max Lip Sync, when the song has word timestamps) → composed video with optional karaoke lyric captions. Bring your own song via `song_url` (with optional lyrics/sections/word timestamps), or pass an inline `song` prompt and Genfire generates the track first (ElevenLabs Music v2.5, billed separately). Cost scales with song length and scene_density — call POST /v1/music-videos/estimate-cost first.",
      "status": "available",
      "is_default": true,
      "limits": {
        "aspect_ratios": [
          "9:16",
          "16:9"
        ],
        "scene_densities": [
          "low",
          "medium",
          "high"
        ],
        "song_duration_sec": {
          "min": 1,
          "max": 300
        },
        "inline_song_duration_ms": {
          "min": 10000,
          "max": 600000
        },
        "reference_images": {
          "max": 8
        },
        "catalogs": [
          "styles"
        ]
      }
    },
    {
      "id": "picture-book.gpt-image-2",
      "object": "model",
      "capability": "picture_book_generation",
      "name": "Picture Book",
      "description": "A complete illustrated picture book from a story idea or script: the free planner writes/splits the text to the age band, then a style anchor, one reference sheet per cast member, every page and both covers render on GPT Image 2 (edit endpoint, anchor + sheets as references) so characters stay consistent. Text is typeset in post (default) or lettered by the model; print formats render at KDP trim + bleed and export as print-ready PDFs (interior, cover, ebook, images zip). Async: poll GET /v1/runs/:id (progress stages included), then read GET /v1/picture-books/:id. Call POST /v1/picture-books/estimate-cost first — cost scales with pages × quality.",
      "status": "available",
      "is_default": true,
      "limits": {
        "age_bands": [
          "board",
          "picture",
          "early-reader"
        ],
        "qualities": [
          "low",
          "medium"
        ],
        "lettering_modes": [
          "typeset",
          "lettered"
        ],
        "pages": {
          "min": 4,
          "max": 48
        },
        "cast": {
          "max": 4
        },
        "export_kinds": [
          "interior-pdf",
          "cover-pdf",
          "ebook-pdf",
          "images-zip"
        ],
        "catalogs": [
          "styles",
          "formats",
          "age_bands"
        ]
      }
    }
  ]
}
GET /models/pricing
models:read

Get per-model base pricing

Returns the BASE credits per unit for every model. This is a base rate: the actual charge also scales with resolution, quality, duration, audio, count, and 3D add-ons. For the EXACT quote of a specific configuration, call POST /v1/models/estimate-cost.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/models/pricing \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "model": "image.nano_banana_2",
      "capability": "image_generation",
      "unit": "per_image",
      "credits": 7,
      "operation_key": "nano_banana_2"
    },
    {
      "model": "video.veo_3_1",
      "capability": "video_generation",
      "unit": "per_second",
      "credits": 30,
      "operation_key": "veo31_fast",
      "notes": "Base per-second rate. Resolution and count scale the cost — call POST /v1/models/estimate-cost for the exact quote. Audio adds a multiplier when generate_audio=true."
    },
    {
      "model": "video.seedance_2_0",
      "capability": "video_generation",
      "unit": "per_second",
      "credits": 30,
      "operation_key": "seedance_2_0",
      "notes": "Base (720p) per-second rate; 1080p ×2.25, 4K ×5.25. Call POST /v1/models/estimate-cost for the exact quote."
    },
    {
      "model": "video.seedance_2_5",
      "capability": "video_generation",
      "unit": "per_second",
      "credits": 47,
      "operation_key": "seedance_2_5",
      "notes": "Base (720p) per-second rate; 480p ×0.47, 1080p ×2.5 (1080p is billed at a higher token rate, so it is NOT 2.0's ×2.25). No 4k tier. Reference-to-video prices on its own keys: seedance_2_5_ref with images only, and seedance_2_5_video_ref (20 credits at 720p, charged per second of OUTPUT and again per second of REFERENCE CLIP, because the provider bills the clip as input tokens) as soon as reference_video_urls carries a clip — which is every Genfire Genjudo motion-transfer and video-edit run. Call POST /v1/models/estimate-cost for the exact quote; it measures the clip."
    },
    {
      "model": "speech.elevenlabs_v4_turbo",
      "capability": "speech_generation",
      "unit": "per_second",
      "credits": 1,
      "operation_key": "elevenlabs_tts_v4_turbo",
      "notes": "Billed seconds ≈ ceil(character_count / 15)."
    }
  ]
}

Notes

  • Unit values: `per_image` (image models, max 4 per request), `per_second` (video / speech / music / SFX / lipsync — speech bills seconds ≈ ceil(chars/15)), `per_generation` (flat, e.g. Lyria music and 3D mesh).
  • `credits` is a BASE rate. Video resolution multiplies it (e.g. Seedance 2.0 1080p ×2.25, 4K ×5.25; Seedance 2.5 480p ×0.47); image 2K ×1.5 / 4K ×3 and GPT Image 2 quality ×6/×22; 3D texturing/PBR/rigging add-ons; audio-off discount on some video. Call POST /v1/models/estimate-cost for the exact number.
POST /models/estimate-cost
models:read

Estimate exact generation cost

Returns the EXACT credits a specific generation config will cost — accounting for resolution, quality, duration, audio, count, and 3D add-ons. Unlike GET /models/pricing (base rate), this equals what will actually be billed. Read-only; no idempotency key required.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

model string optional

Model alias from GET /v1/models, required unless marketing_template_id selects it.

marketing_template_id string optional

Use the same template and product/avatar inputs as generation. Applies its defaults before quoting.

product_image_url string optional

Product reference matching the generation request.

avatar_image_url string optional

Creator reference matching the generation request.

product_id string optional

Stored product matching the generation request.

avatar_id string optional

Stored avatar matching the generation request.

task string optional

Video task: reference, editing or extension. Source-driven editing measures the source clip server-side.

source_video_url string optional

Source video URL or owned run reference for editing. Must be the same value the generation request sends.

reference_video_urls array optional

Reference clips. Presence affects model routing and rates; source-driven edits measure their duration.

reference_video_trims array optional

Per-clip windows over reference_video_urls (`[{ index | url, start, end | duration }]`), as sent to generation. Reference edits bill the trimmed pool.

reference_image_urls array optional

Image references matching generation.

reference_audio_urls array optional

Audio references matching generation.

resolution string optional

Video/image resolution (e.g. 720p, 1080p, 4k, 2K).

duration number optional

Video duration in seconds (also used for lipsync audio when no audio_url). Send it (and `aspect_ratio`, `follow_source`, `keep_source_audio`) exactly as the generation request does: on `video.seedance_2_5` with one reference clip, task `reference` (or none) and the ratio omitted, `auto` or the clip's own, an omitted (or `auto`) duration is priced on the clip's measured length — the length that run renders and bills.

generate_audio boolean optional

Whether audio is generated (affects some video models).

aspect_ratio string optional

Video aspect ratio, exactly as the generation request sends it. On a one-clip `video.seedance_2_5` reference run it decides whether an omitted duration follows the clip (ratio omitted, `auto` or the clip's own) or is 5s — so it can change the price.

keep_source_audio boolean optional

Genjudo (`video.seedance_2_5`), exactly as the generation request sends it.

follow_source boolean optional

Genjudo (`video.seedance_2_5`), exactly as the generation request sends it: replaces `duration` with the clip's own measured length.

count number optional

Number of outputs (video/image). `video.seedance_2_5` renders one video per request — a count above 1 is clamped to 1 and quoted as one video.

quality string optional

Image quality tier — GPT Image 2 / GPT Image 2.5 / Genfire (low/medium/high), Ideogram 4.5 (low/medium/high) and Grok Imagine 2.0 (low/medium).

should_texture boolean optional

3D: include base-color textures (default true).

enable_pbr boolean optional

3D: include PBR maps.

enable_rigging boolean optional

3D: include auto-rigging.

text string optional

Speech text (character_count derived from length).

character_count number optional

Speech character count (alternative to text).

duration_seconds number optional

Music / SFX duration in seconds.

audio_url string optional

Lipsync: audio URL (duration estimated when duration omitted).

loras array optional

Video: the trained models the generation will run with (`[{ id, scale? }]`, up to 3). Moves the quote onto the Hailuo 03 LoRA rate — `video.hailuo_03` only; any other model returns 400 `invalid_loras`. Training itself is quoted by POST /v1/loras/estimate-cost.

curl Example

curl https://api.genfire.ai/v1/models/estimate-cost \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "model": "video.seedance_2_0",
  "resolution": "1080p",
  "duration": 15,
  "generate_audio": true
}'

Response Example

{
  "object": "cost_estimate",
  "model": "video.seedance_2_0",
  "capability": "video_generation",
  "credits": 1013,
  "unit": "total",
  "breakdown": {
    "operation_key": "seedance_2_0",
    "base_rate": 30,
    "duration_seconds": 15,
    "resolution": "1080p",
    "resolution_multiplier": 2.25,
    "audio_multiplier": 1,
    "video_count": 1
  },
  "quote_id": "quote_9f1c2ab34d5e6f70",
  "expires_at": "2026-09-12T18:24:00.000Z",
  "quote_token": "eyJxIjoicXVvdGVfOWYxYzJhYjM0ZDVlNmY3MCIsLi4ufQ.f3Kb2p9wQm1sZ0yVxT4uR7NcH6L8dJeA5gXqW0oP1sY"
}

Request Example Body

{
  "model": "video.seedance_2_0",
  "resolution": "1080p",
  "duration": 15,
  "generate_audio": true
}

Notes

  • The returned `credits` is the exact charge for the given config — quote == charge.
GET /audio/voices
models:read

List voices for speech generation

Returns the voice ids accepted by `voice_id` on `/audio/speech`. By default this lists the voices you have cloned (their ids look like `clone_<id>`); a cloned voice id is otherwise only shown once at clone time in the dashboard, so this is the way to discover it programmatically. Pass `?include=stock` to also return built-in ElevenLabs stock voices.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

include query optional

Comma-separated extra sources to include. Currently only `stock` is supported, which appends built-in ElevenLabs voices.

search query optional

With `include=stock`: free-text search over the stock catalog (name, description, labels).

gender query optional

With `include=stock`: filter stock voices by gender label (e.g. `male`, `female`, `neutral`).

age query optional

With `include=stock`: filter by age label (`young`, `middle_aged`, `old`).

accent query optional

With `include=stock`: filter by accent label (e.g. `american`, `british`, `australian`).

language query optional

With `include=stock`: ISO 639-1 language the voice is verified for (e.g. `es`).

use_case query optional

With `include=stock`: filter by use-case label (e.g. `narration`, `conversational`, `characters`).

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/audio/voices \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "clone_abc123",
      "object": "voice",
      "name": "My Cloned Voice",
      "type": "cloned",
      "provider": "cloned",
      "preview_url": "https://cdn.genfire.ai/voice-clones/preview-abc123.wav",
      "description": null,
      "labels": null,
      "created_at": "2026-05-01T12:00:00.000Z"
    },
    {
      "id": "21m00Tcm4TlvDq8ikWAM",
      "object": "voice",
      "name": "Rachel",
      "type": "stock",
      "provider": "elevenlabs",
      "preview_url": "https://storage.googleapis.com/eleven-public-prod/premade/voices/21m00Tcm4TlvDq8ikWAM/sample.mp3",
      "description": "A calm, young female voice.",
      "labels": {
        "gender": "female",
        "accent": "american",
        "age": "young",
        "use_case": "narration"
      },
      "created_at": null
    }
  ]
}

Notes

  • Cloned voices are scoped to the authenticated account — you only ever see your own.
  • Stock voices are large and account-global, so they are excluded unless `include=stock` is passed.

Reference

Usage

GET /usage
runs:read

Get aggregated API usage

Aggregate API usage for the authenticated account over a date range. Returns totals (credits spent, run counts) plus a breakdown grouped by model, capability, or day. Default range is the last 30 days; max range is 365 days.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

start_date query optional

Inclusive lower bound (ISO date or full timestamp). Defaults to 30 days ago.

end_date query optional

Exclusive upper bound. Defaults to now.

group_by query optional

Bucketing for the breakdown: `model`, `capability`, `day`, or `none`.

Default: model

capability query optional

Optional filter, e.g. `video_generation`.

team_id query optional

Report the WORKSPACE pool's spend instead of your own (`GET /v1/teams`). Requires the `teams:read` scope; 404 `invalid_team_id` when you are not a member. Same date-range semantics.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/usage \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "usage_summary",
  "period": {
    "start": "2026-04-01T00:00:00.000Z",
    "end": "2026-04-30T23:59:59.999Z"
  },
  "group_by": "model",
  "totals": {
    "credits_spent": 4823,
    "runs_count": 187,
    "successful_runs": 178,
    "failed_runs": 9
  },
  "breakdown": [
    {
      "group": "video.veo_3_1",
      "credits_spent": 2400,
      "runs_count": 30,
      "successful_runs": 30,
      "failed_runs": 0,
      "avg_credits_per_run": 80
    },
    {
      "group": "image.nano_banana_2",
      "credits_spent": 423,
      "runs_count": 141,
      "successful_runs": 138,
      "failed_runs": 3,
      "avg_credits_per_run": 3
    }
  ]
}

Notes

  • Buckets are sorted by `credits_spent` descending, except `group_by=day` which is sorted chronologically.
  • Counts only include runs that hit `/v1` endpoints. Dashboard generations and workflow runs initiated outside the public API are not included.
  • With `team_id` the numbers come from the WORKSPACE LEDGER, not from run docs: a pool-billed run's cost lands on the team, and the members spending it are not all on your account. So the summary covers every member's spend, `capability` does not apply, and every bucket is an operation key (the dimension a pool is actually spent along). Refunds are already netted out.

Reference

Uploads

POST /uploads
uploads:write

Create a signed upload URL

Allocates a pre-signed PUT URL so a client can upload a local file directly to Genfire storage without proxying bytes through the API. Use the returned `asset_url` as `image_url`, `video_url`, or `audio_url` in subsequent generation requests. With `resumable: true` the response also carries `upload_session_url`, a Google Cloud Storage RESUMABLE upload session for the same object, for a client that cannot send the file in one request: PUT it in chunks with `Content-Range: bytes <first>-<last>/<size_bytes>` (every chunk but the last a multiple of 256 KiB), and the asset is readable at `asset_url` once the final chunk lands.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

filename string required

Original filename. The extension is preserved in the stored object path.

content_type string optional

MIME type for the upload. Pinned into the signed URL — the PUT must send the same `Content-Type`.

Default: application/octet-stream

size_bytes number optional

Declared file size. Optional, and rejected if greater than the 5 GB ceiling — except with `resumable: true`, where it is REQUIRED, a whole number of bytes, at most 104857600 (100 MiB), and pinned into the session as the object's exact length.

resumable boolean optional

Also open a resumable upload session (`upload_session_url`) for chunked uploads. Requires `size_bytes`. Default false.

curl Example

curl https://api.genfire.ai/v1/uploads \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "filename": "reference.png",
  "content_type": "image/png",
  "size_bytes": 124000
}'

Response Example

{
  "asset_id": "asset_8f3c4a2b1d6e7f9a0b1c2d3e",
  "upload_url": "https://storage.googleapis.com/genfire-uploads/...?X-Goog-Signature=...",
  "asset_url": "https://storage.googleapis.com/genfire-uploads/...?X-Goog-Signature=...",
  "content_type": "image/png",
  "expires_at": "2026-05-12T02:21:25.985Z"
}

Request Example Body

{
  "filename": "reference.png",
  "content_type": "image/png",
  "size_bytes": 124000
}

Notes

  • PUT the file bytes to `upload_url` within 15 minutes. The signed URL is pinned to the `content_type` you submitted — sending a different `Content-Type` header will be rejected by the storage layer.
  • `asset_url` is a 7-day signed READ URL. Re-upload the asset before this point if you need long-lived references.
  • Maximum upload size is 5 GB (100 MiB for a resumable upload).
  • Resumable: the response adds `resumable: true` and `upload_session_url`. The session URI is itself the credential — keep it server-side. GCS answers each intermediate chunk with `308` and a `Range` header saying what it has; the final chunk (whose range ends at `size_bytes - 1`) answers `200`/`201`. The total in `Content-Range` must equal `size_bytes`. The session expires after 7 days; `upload_url` stays usable for a single-request PUT either way.

Reference

Influencers

GET /influencers
influencers:read

List your trained influencers

Returns the influencer characters owned by the authenticated account that are in `ready` status. Use the returned `id` and `handle` in the `mentions` array of an image generation request to inject the character.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/influencers \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "model_abc123def456",
      "object": "influencer",
      "handle": "sarah",
      "display_name": "Sarah Chen",
      "status": "ready",
      "generation_state": "ready",
      "source_type": "uploaded",
      "preview_url": "https://firebasestorage.googleapis.com/...",
      "face_url": "https://firebasestorage.googleapis.com/...",
      "sheet_url": "https://firebasestorage.googleapis.com/...",
      "builder_version": null,
      "builder_recipe": null,
      "created_at": "2026-04-12T10:30:00.000Z",
      "updated_at": "2026-04-12T10:35:00.000Z"
    }
  ]
}

Notes

  • Drafts and archived influencers are NOT returned. Train and finalize them in the dashboard before they can be referenced.
  • A character made in the Influencer Studio builder (`recipe` on POST /v1/influencers) carries `builder_version: 2` and the `builder_recipe` it was made from — send that recipe back to recreate it. Both are null on every other character.
GET /influencers/pricing
influencers:read

What creating an influencer costs

The credits one POST /v1/influencers charges, per creation mode, priced with the same estimator the create route checks your balance against. Builder mode (`recipe`) renders ONE three-view character sheet: `builder.from_scratch` from the recipe alone, `builder.from_photo` with a `reference_photo_url`. The legacy modes render a hero photo and then a reference sheet: `legacy.from_photos` (`photo_urls`) and `legacy.from_appearance` (`appearance`). A mode whose pricing could not be read is `null`. Free to call.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/influencers/pricing \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "influencer_pricing",
  "builder": {
    "from_scratch": 6,
    "from_photo": 12
  },
  "legacy": {
    "from_photos": 24,
    "from_appearance": 18
  }
}
GET /influencers/{influencerId}
influencers:read

Get a single influencer by id

Returns full details for one influencer the authenticated account owns.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

influencerId path required

Influencer id from `GET /v1/influencers`.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/influencers/{influencerId} \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "id": "model_abc123def456",
  "object": "influencer",
  "handle": "sarah",
  "display_name": "Sarah Chen",
  "status": "ready",
  "generation_state": "ready",
  "source_type": "uploaded",
  "preview_url": "https://firebasestorage.googleapis.com/...",
  "face_url": "https://firebasestorage.googleapis.com/...",
  "sheet_url": "https://firebasestorage.googleapis.com/...",
  "builder_version": null,
  "builder_recipe": null,
  "created_at": "2026-04-12T10:30:00.000Z",
  "updated_at": "2026-04-12T10:35:00.000Z"
}

Notes

  • 404 if the influencer does not exist or is not owned by the authenticated account.
POST /influencers
influencers:write

Create an influencer (from photos, from scratch, or in the builder)

Creates a reusable influencer character in one of three modes: 1. **From photos** — supply `photo_urls` (1–8 reference photos of the person; one clear, well-lit face is enough). The bytes are copied into durable storage and the identity is rendered from them. 2. **From scratch** — supply `appearance` (no photos). A brand-new person is generated from the described traits. 3. **Builder** — supply `recipe` + `prompt` (the Influencer Studio builder: a trait recipe and the casting prompt composed from it), optionally with ONE face photo as `reference_photo_url`. Humans or creatures, in any styling tier. From photos and from scratch render TWO images that lock the identity: a hero photo, then a 4-panel character reference sheet derived from it (`face_url` and `sheet_url` on the influencer, populated in that order). The builder renders ONE three-view character sheet (close-up portrait | rear | front) — `sheet_url`, with `face_url` staying null. Provide exactly one of `photo_urls`, `appearance` or `recipe`. The call returns immediately with `status: "creating"`; the generation runs asynchronously (~60–90s). Poll `GET /v1/influencers/{id}` until `status` is `ready` (or `failed`). Billable: the generations charge credits (GET /v1/influencers/pricing lists what each mode costs).

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Body Fields

handle string optional

Short @-mention handle (letters, digits, underscores), e.g. `maya`. Lowercased automatically. Must be unique within your account. This is the influencer's only name — the `display_name` in responses is derived from it. REQUIRED with `photo_urls` or `appearance`; OPTIONAL with `recipe`, where a blank handle is derived from the recipe (e.g. `retro_beehive`, suffixed `_2`, `_3`… until free).

photo_urls string[] optional

FROM-PHOTOS mode. 1–8 absolute https URLs of reference photos. Upload local files via `POST /v1/uploads` first and pass the returned `asset_url`s here. Mutually exclusive with `appearance`.

appearance object optional

FROM-SCRATCH mode. Generate a new person from traits. Fields: `gender` (string, required — e.g. "woman", "man"), `heritage` (string, required — race/ethnicity, e.g. "korean"), `age` (string, required — one of `18-21`, `21-25`, `25-30`, `30-40`, `40+`), `prompt` (string, optional — free-text descriptor, e.g. "freckles, platinum bob, green eyes, editorial look"). Mutually exclusive with `photo_urls`.

recipe object optional

BUILDER mode. The Influencer Studio trait recipe: `tier` (required — `average`, `bold` or `extreme`), `species` (optional — `mantis`, `ant` or `dog` for a creature; omit for a human), `selection` (required — an object of section key → up to 4 option keys, at most 18 sections, keys `[a-z_]`, options `[a-z0-9_]`, e.g. `{ "hairstyle": ["beehive"] }`), `brief` (optional, ≤4000 chars). Stored on the character as `builder_recipe` for Recreate. Requires `prompt`; cannot be combined with `photo_urls` or `appearance`.

prompt string optional

BUILDER mode, required there: the casting prompt the sheet renders from (≤4000 chars), composed from the recipe. Only the sheet render reads it — it is never appended to later @mention generations.

reference_photo_url string optional

BUILDER mode, optional: ONE face photo (absolute https URL, e.g. an `asset_url` from POST /v1/uploads). The sheet restyles this person instead of inventing one, and is priced as an edit.

curl Example

curl https://api.genfire.ai/v1/influencers \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "handle": "maya",
  "appearance": {
    "gender": "woman",
    "heritage": "korean",
    "age": "21-25",
    "prompt": "freckles, platinum blonde bob, green eyes, editorial fashion look"
  }
}'

Response Example

{
  "id": "model_abc123def456",
  "object": "influencer",
  "handle": "maya",
  "display_name": "Maya",
  "status": "creating",
  "generation_state": "draft",
  "source_type": "generated",
  "preview_url": null,
  "face_url": null,
  "sheet_url": null,
  "builder_version": null,
  "builder_recipe": null,
  "created_at": "2026-06-18T10:30:00.000Z",
  "updated_at": "2026-06-18T10:30:00.000Z"
}

Request Example Body

{
  "handle": "maya",
  "appearance": {
    "gender": "woman",
    "heritage": "korean",
    "age": "21-25",
    "prompt": "freckles, platinum blonde bob, green eyes, editorial fashion look"
  }
}

Notes

  • Requires the `influencers:write` scope and an `Idempotency-Key` header.
  • Provide exactly one of `photo_urls` (clone from photos), `appearance` (generate from scratch) or `recipe` (the builder). `recipe` with either of the others is a 400 `invalid_request`.
  • Builder mode: `{ "recipe": {"tier":"extreme","selection":{"gender":["female"],"style":["retro"],"hairstyle":["beehive"],"age":["mature"]},"brief":"jazz-club singer"}, "prompt": "A woman in her fifties with a towering beehive…", "reference_photo_url": "https://…/face.jpg" }`. The response is the influencer plus `credits_estimate` (what the sheet will charge, null if pricing could not be read), with `builder_version: 2` and the stored `builder_recipe`. Your balance is checked first: 402 `insufficient_credits` (with `required_credits` and `current_credits`) and nothing is created when it cannot cover the sheet. A typed `handle` that is taken is a 409 `handle_taken`.
  • Asynchronous: the response is `status: "creating"`. Poll `GET /v1/influencers/{id}` until `ready`.
  • Creation produces TWO images in sequence — the hero `face_url`, then the `sheet_url` built from it. Each appears on the polled record as soon as it lands, and `generation_state` advances `draft` → `face_generated` → `sheet_generated` → `ready`, so a UI can show per-artefact progress instead of one opaque spinner.
  • Billable: generating the reference sheet (and, for from-scratch, the hero photo) charges credits against your account.
  • Once `ready`, reference the influencer in image generation via the `mentions` array using the returned `id` and `handle`.

Reference

Projects

GET /projects
projects:read

List your projects

Returns every project the authenticated account can see — its own, plus any shared with a workspace it belongs to. A project is a named folder of work (videos, images, audio, elements); the same projects the Video Studio shows in its rail. Folders are projects with `parent_id` set and nest exactly one level.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

parent_id query optional

Filter. A root project id lists only that project's folders; the literal `root` lists only top-level projects. Omit for the whole tree.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/projects \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "col_abc123",
      "object": "project",
      "name": "Summer Campaign",
      "description": null,
      "brief": "Warm, sun-bleached, shot on 35mm.",
      "parent_id": null,
      "is_folder": false,
      "cover_url": "https://firebasestorage.googleapis.com/...",
      "team_id": null,
      "role": "owner",
      "item_count": 12,
      "created_at": "2026-08-01T10:30:00.000Z",
      "updated_at": "2026-08-24T18:02:00.000Z"
    },
    {
      "id": "col_def456",
      "object": "project",
      "name": "Scene 01",
      "description": null,
      "brief": null,
      "parent_id": "col_abc123",
      "is_folder": true,
      "cover_url": null,
      "team_id": null,
      "role": "owner",
      "item_count": 4,
      "created_at": "2026-08-02T09:00:00.000Z",
      "updated_at": "2026-08-24T18:02:00.000Z"
    }
  ]
}

Notes

  • `role` is the authenticated caller's role on the project: `owner` when they created it, otherwise their role on the workspace it is shared with.
  • Pass a project id as `project_id` on a generation request to file that generation's output into it automatically — see the generation endpoints.
GET /projects/{projectId}
projects:read

Open a project and list what is in it

Returns one project plus its filed `items`, each hydrated to a real media `url` you can pass straight back into a generation request (as `image_url`, `reference_image_urls`, or a lip-sync `video_url`). Also returns the project `brief` — the creative direction the user wrote for it.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

projectId path required

Project id from `GET /v1/projects`.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/projects/{projectId} \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "id": "col_abc123",
  "object": "project",
  "name": "Summer Campaign",
  "description": null,
  "brief": "Warm, sun-bleached, shot on 35mm.",
  "parent_id": null,
  "is_folder": false,
  "cover_url": "https://firebasestorage.googleapis.com/...",
  "team_id": null,
  "role": "owner",
  "item_count": 2,
  "items": [
    {
      "asset_id": "img_abc123",
      "asset_type": "image",
      "title": "bottle on warm sand, 35mm",
      "url": "https://firebasestorage.googleapis.com/...",
      "thumbnail_url": "https://firebasestorage.googleapis.com/...",
      "added_at": "2026-08-24T18:02:00.000Z"
    },
    {
      "asset_id": "vid_def456",
      "asset_type": "video",
      "title": "hero pour, slow motion",
      "url": "https://firebasestorage.googleapis.com/...",
      "thumbnail_url": "https://firebasestorage.googleapis.com/...",
      "added_at": "2026-08-24T18:05:00.000Z"
    }
  ],
  "created_at": "2026-08-01T10:30:00.000Z",
  "updated_at": "2026-08-24T18:05:00.000Z"
}

Notes

  • An item whose asset is still generating, or has since been deleted, comes back with `url: null` rather than failing the read.
  • Items are capped at 200 per response; a project holds at most 500.
  • 404 if the project does not exist or the account has no access to it.
POST /projects
projects:write

Create a project or a folder

Creates a project. Pass `parent_id` to create a FOLDER inside an existing root project instead — folders nest one level only, so a folder cannot contain folders. Free and synchronous.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

name string required

Project name, e.g. `Summer Campaign` or `Scene 01`.

description string optional

Optional short description.

parent_id string optional

Create this as a folder inside the given ROOT project. The parent must be a root the caller can edit.

curl Example

curl https://api.genfire.ai/v1/projects \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Summer Campaign"
}'

Response Example

{
  "id": "col_abc123",
  "object": "project",
  "name": "Summer Campaign",
  "description": null,
  "brief": null,
  "parent_id": null,
  "is_folder": false,
  "cover_url": null,
  "team_id": null,
  "role": "owner",
  "item_count": 0,
  "items": [],
  "created_at": "2026-08-26T10:30:00.000Z",
  "updated_at": "2026-08-26T10:30:00.000Z"
}

Request Example Body

{
  "name": "Summer Campaign"
}

Notes

  • A folder created inside a workspace-shared project inherits the share.
  • 409-equivalent 400 if `parent_id` names a folder rather than a root project.
PATCH /projects/{projectId}
projects:write

Rename a project, edit its brief, or re-file it

Partial update. Supply any of `name`, `brief` or `parent_id`.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

projectId path required

Project id from `GET /v1/projects`.

Body Fields

name string optional

New project name.

brief string optional

The project's creative brief, shown in the studio and returned by `GET /v1/projects/{projectId}`.

parent_id string | null optional

Move this project under a root project, or `null` to promote it to a root. One-level nesting is enforced: a project that already has folders cannot become a folder.

curl Example

curl https://api.genfire.ai/v1/projects/{projectId} \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "brief": "Warm, sun-bleached, shot on 35mm."
}'

Response Example

{
  "id": "col_abc123",
  "object": "project",
  "name": "Summer Campaign",
  "description": null,
  "brief": "Warm, sun-bleached, shot on 35mm.",
  "parent_id": null,
  "is_folder": false,
  "cover_url": "https://firebasestorage.googleapis.com/...",
  "team_id": null,
  "role": "owner",
  "item_count": 12,
  "created_at": "2026-08-01T10:30:00.000Z",
  "updated_at": "2026-08-26T10:31:00.000Z"
}

Request Example Body

{
  "brief": "Warm, sun-bleached, shot on 35mm."
}

Notes

  • Requires edit access: the owner, or an owner/admin/editor of the workspace the project is shared with.
DELETE /projects/{projectId}
projects:write

Delete a project

Deletes the project. The media it held is NOT deleted — it stays in your library and simply stops being filed here.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

projectId path required

Project id from `GET /v1/projects`.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/projects/{projectId} \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "id": "col_abc123",
  "object": "project",
  "deleted": true
}

Notes

  • Owner only. A workspace admin who did not create the project cannot delete it.
POST /projects/{projectId}/items
projects:write

File assets into a project

Adds existing assets to a project. Use this for work that already exists; for NEW generations pass `project_id` on the generation request instead and the output files itself when the run completes.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

projectId path required

Project id from `GET /v1/projects`.

Body Fields

items object[] required

Assets to file. Each is `{ asset_id, asset_type }` where `asset_type` is one of `video`, `image`, `audio`, `element`, plus an optional `title` and `thumbnail_url`. Image and video ids come from a completed run (`resource_id`, or `internal_refs.image_ids`); element ids from `GET /v1/elements`.

curl Example

curl https://api.genfire.ai/v1/projects/{projectId}/items \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "items": [
    {
      "asset_id": "img_abc123",
      "asset_type": "image",
      "title": "bottle on warm sand"
    },
    {
      "asset_id": "elem_abc123",
      "asset_type": "element"
    }
  ]
}'

Response Example

{
  "id": "col_abc123",
  "object": "project",
  "name": "Summer Campaign",
  "item_count": 14,
  "added": 2,
  "updated_at": "2026-08-26T10:32:00.000Z"
}

Request Example Body

{
  "items": [
    {
      "asset_id": "img_abc123",
      "asset_type": "image",
      "title": "bottle on warm sand"
    },
    {
      "asset_id": "elem_abc123",
      "asset_type": "element"
    }
  ]
}

Notes

  • De-duplicated by `asset_id`: re-filing something already in the project is a no-op, and `added` reports how many were actually new.
  • A project holds 500 items; exceeding it is a 400.
  • Filing into a workspace-shared project makes your own assets readable by that workspace. You can only widen access to assets you own.
DELETE /projects/{projectId}/items
projects:write

Unfile assets from a project

Removes assets from a project. The media itself is not deleted — it stays in your library.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

projectId path required

Project id from `GET /v1/projects`.

Body Fields

asset_ids string[] required

Asset ids to unfile, from `GET /v1/projects/{projectId}` items.

curl Example

curl https://api.genfire.ai/v1/projects/{projectId}/items \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "id": "col_abc123",
  "object": "project",
  "name": "Summer Campaign",
  "item_count": 13,
  "removed": 1,
  "updated_at": "2026-08-26T10:33:00.000Z"
}

Request Example Body

{
  "asset_ids": [
    "img_abc123"
  ]
}

Notes

  • An asset removed from the last workspace project holding it loses that workspace's read access again.

Reference

Elements

GET /elements
elements:read

List your reusable image elements

Returns the named image "elements" (reusable props — a product, logo, object) owned by the authenticated account. Reference an element in a video prompt by writing `@<handle>`.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/elements \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "elem_abc123",
      "object": "element",
      "handle": "redbottle",
      "name": "Red Bottle",
      "image_url": "https://firebasestorage.googleapis.com/...",
      "thumbnail_url": "https://firebasestorage.googleapis.com/...",
      "source_type": "upload",
      "aspect_ratio": "1:1",
      "created_at": "2026-06-29T10:30:00.000Z",
      "updated_at": "2026-06-29T10:30:00.000Z"
    }
  ]
}

Notes

  • Element `@<handle>` mentions resolve only on reference-capable video models (Seedance, Wan 3.0, Veo 3.1 reference, Gemini Omni Flash reference, Hailuo 03 reference, or Grok reference). The element image is appended to the reference pool and the handle is rewritten for you — to "the <name>" where references bind by description, or to `@Image{n}` where they bind by POSITION (Wan 3.0, Hailuo 03 reference). They are ignored by image models and by Kling/Happy Horse, whose own tokens ARE the array index.
GET /elements/{elementId}
elements:read

Get a single element by id

Returns full details for one element the authenticated account owns.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

elementId path required

Element id from `GET /v1/elements`.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/elements/{elementId} \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "id": "elem_abc123",
  "object": "element",
  "handle": "redbottle",
  "name": "Red Bottle",
  "image_url": "https://firebasestorage.googleapis.com/...",
  "thumbnail_url": "https://firebasestorage.googleapis.com/...",
  "source_type": "upload",
  "aspect_ratio": "1:1",
  "created_at": "2026-06-29T10:30:00.000Z",
  "updated_at": "2026-06-29T10:30:00.000Z"
}

Notes

  • 404 if the element does not exist or is not owned by the authenticated account.
POST /elements
elements:write

Create an element from an image

Creates a reusable named image element from a single image URL. Synchronous and free (no generation): the element is returned immediately in `ready` status. Once created, write `@<handle>` in a video generation `prompt` to drop the element image into the scene on reference-capable models.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

name string required

Human-friendly name, e.g. `Red Bottle`. Also used as the in-prompt phrase ("the Red Bottle") when the `@handle` is resolved.

image_url string required

Absolute https URL of the element image. Upload a local file via `POST /v1/uploads` first and pass the returned `asset_url`.

handle string optional

Short @-mention handle (letters, digits, underscores), e.g. `redbottle`. Lowercased automatically. Auto-derived from `name` if omitted. Must be unique within your account.

aspect_ratio string optional

Optional aspect ratio of the image, e.g. `1:1`, `9:16`. Informational only.

project_id string optional

File the new element into one of your PROJECTS (`GET /v1/projects`), the same way the studio files an element created while a project is open. Access is checked before the element is created; the response echoes `project_id` (null if filing failed).

curl Example

curl https://api.genfire.ai/v1/elements \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Red Bottle",
  "handle": "redbottle",
  "image_url": "https://storage.googleapis.com/.../bottle.png"
}'

Response Example

{
  "id": "elem_abc123",
  "object": "element",
  "handle": "redbottle",
  "name": "Red Bottle",
  "image_url": "https://firebasestorage.googleapis.com/...",
  "thumbnail_url": "https://firebasestorage.googleapis.com/...",
  "source_type": "upload",
  "aspect_ratio": "1:1",
  "created_at": "2026-06-29T10:30:00.000Z",
  "updated_at": "2026-06-29T10:30:00.000Z"
}

Request Example Body

{
  "name": "Red Bottle",
  "handle": "redbottle",
  "image_url": "https://storage.googleapis.com/.../bottle.png"
}

Notes

  • Requires the `elements:write` scope.
  • Not billable — no generation runs; the supplied image URL is stored as the element.
  • To use: write `@<handle>` in a `POST /v1/videos/generations` prompt on a reference-capable model (Seedance, Veo 3.1 reference, or Grok reference).
DELETE /elements/{elementId}
elements:write

Delete an element

Removes an element from your library. The underlying image is not affected; only the named `@handle` reference is deleted.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

elementId path required

Element id from `GET /v1/elements`.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/elements/{elementId} \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "id": "elem_abc123",
  "object": "element",
  "deleted": true
}

Notes

  • 404 if the element does not exist or is not owned by the authenticated account.

Reference

Brands

POST /brands/ingestions
brands:write

Create a brand profile from a website URL

Ingests a website into a full brand profile: renders the site in a headless browser, captures desktop/mobile screenshots, extracts the logo, colors, and fonts from the rendered page, scrapes product data, and runs vision + copy analysis to build a structured brand voice. Asynchronous: returns a queued run — poll `GET /v1/runs/{id}`; the completed output carries `brand_id`.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

url string required

The brand or product website URL, e.g. `https://example.com`. A bare domain is accepted (https is assumed).

curl Example

curl https://api.genfire.ai/v1/brands/ingestions \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://example.com"
}'

Response Example

{
  "id": "run_abc123",
  "object": "run",
  "capability": "brand_ingestion",
  "status": "queued",
  "output": null
}

Request Example Body

{
  "url": "https://example.com"
}

Notes

  • Requires the `brands:write` scope and an `Idempotency-Key` header.
  • Free — brand ingestion never charges credits.
  • Typical duration is 30–90 seconds. When the run completes, `output.brand_id` identifies the brand; fetch it with `GET /v1/brands/{brandId}`.
  • Ingestion visits at most the pasted URL plus 2 discovered pages (about/product), honoring robots.txt for discovered pages.
POST /brands
brands:write

Create a brand from supplied fields

Creates a brand profile from fields you provide. Unlike `POST /v1/brands/ingestions`, this never fetches a website or any third-party URL — `website_url` is stored as metadata only. Use it when you already have the brand details, or on surfaces where URL ingestion is not permitted. Synchronous: returns the created brand.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

name string required

Brand name.

tagline string optional

Short positioning line.

description string optional

What the brand sells and who it serves.

website_url string optional

Stored for reference only — this endpoint never visits it.

logo_url string optional

URL of an already-hosted logo image.

icon_url string optional

URL of an already-hosted icon or favicon.

colors array optional

Palette entries, e.g. `[{ "hex": "#FF5A1F", "role": "primary" }]`.

fonts array optional

Typography entries, e.g. `[{ "name": "Inter", "role": "heading" }]`.

voice object optional

Structured brand voice (tone, do/dont, vocabulary).

style string optional

Free-text visual style direction.

image_urls array optional

Reference imagery already hosted elsewhere.

default_language string optional

BCP-47 language tag, e.g. `en-US`.

default_country string optional

ISO 3166-1 alpha-2 country code, e.g. `US`.

curl Example

curl https://api.genfire.ai/v1/brands \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Acme",
  "tagline": "Ship faster",
  "colors": [
    {
      "hex": "#FF5A1F",
      "role": "primary"
    }
  ],
  "fonts": [
    {
      "name": "Inter",
      "role": "heading"
    }
  ]
}'

Response Example

{
  "id": "brand_abc123",
  "object": "brand",
  "name": "Acme",
  "tagline": "Ship faster",
  "colors": [
    {
      "hex": "#FF5A1F",
      "role": "primary"
    }
  ],
  "status": "ready",
  "created_at": "2026-08-13T01:00:00.000Z",
  "updated_at": "2026-08-13T01:00:00.000Z"
}

Request Example Body

{
  "name": "Acme",
  "tagline": "Ship faster",
  "colors": [
    {
      "hex": "#FF5A1F",
      "role": "primary"
    }
  ],
  "fonts": [
    {
      "name": "Inter",
      "role": "heading"
    }
  ]
}

Notes

  • Requires the `brands:write` scope and an `Idempotency-Key` header.
  • Free — creating a brand never charges credits.
  • Add products afterwards with `POST /v1/brands/{brandId}/products`, and refine fields with `PATCH /v1/brands/{brandId}`.
GET /brands
brands:read

List your brands

Returns the brand profiles owned by the authenticated account: identity, visual kit (logo, colors, fonts), structured voice, screenshots, and status.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/brands \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "brand_abc123",
      "object": "brand",
      "name": "Acme",
      "tagline": "Ship faster",
      "website_url": "https://acme.com",
      "logo_url": "https://firebasestorage.googleapis.com/...",
      "colors": [
        {
          "hex": "#FF5A1F",
          "role": "primary"
        }
      ],
      "fonts": [
        {
          "name": "Inter",
          "role": "heading",
          "source": "google-fonts",
          "google_fonts_url": "https://fonts.googleapis.com/css2?family=Inter"
        }
      ],
      "status": "ready",
      "created_at": "2026-07-21T10:30:00.000Z",
      "updated_at": "2026-07-21T10:31:30.000Z"
    }
  ]
}
GET /brands/{brandId}
brands:read

Get a brand with its products

Returns the full brand profile — identity, colors, fonts, structured voice (tone, audience, value props, words to use/avoid), screenshots — plus its scraped product records.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

brandId path required

Brand id from `GET /v1/brands` or a completed ingestion run's `output.brand_id`.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/brands/{brandId} \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "id": "brand_abc123",
  "object": "brand",
  "name": "Acme",
  "voice": {
    "tone_adjectives": [
      "confident",
      "direct"
    ],
    "formality": 3,
    "target_audience": "Small e-commerce teams",
    "value_props": [
      "Launch ads in minutes"
    ],
    "summary": "Write like a helpful expert: short sentences, concrete outcomes, no hype."
  },
  "products": [
    {
      "id": "prod_1",
      "object": "brand_product",
      "name": "Acme Starter Kit",
      "images": [
        "https://..."
      ],
      "price": "49.00"
    }
  ],
  "status": "ready"
}

Notes

  • While ingestion is in progress `status` is `ingesting`; profile fields fill in when it completes.
  • 404 if the brand does not exist or is not owned by the authenticated account.
PATCH /brands/{brandId}
brands:write

Edit a brand

Updates editable brand fields (name, tagline, description, colors, voice, style, logo_url, icon_url, default_language, default_country). Fields not present in the body are left unchanged.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

brandId path required

Brand id.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/brands/{brandId} \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "tagline": "Ship faster",
  "colors": [
    {
      "hex": "#FF5A1F",
      "role": "primary"
    }
  ]
}'

Response Example

{
  "id": "brand_abc123",
  "object": "brand",
  "name": "Acme",
  "tagline": "Ship faster",
  "status": "ready"
}

Request Example Body

{
  "tagline": "Ship faster",
  "colors": [
    {
      "hex": "#FF5A1F",
      "role": "primary"
    }
  ]
}
DELETE /brands/{brandId}
brands:write

Delete a brand

Deletes a brand profile and its product records.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

brandId path required

Brand id.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/brands/{brandId} \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "id": "brand_abc123",
  "object": "brand",
  "deleted": true
}

Reference

Captures

POST /captures
images:write

Screenshot a website

Renders a URL in a real headless browser and returns stored screenshots — one per viewport. This is brand ingestion's renderer on its own: nothing is inferred, analysed or described, so what comes back is what the page actually drew, at the sizes you asked for. Use it to grab a competitor's landing page as a reference image, feed a real site into an image edit, or snapshot your own page before and after a change. The shots are hosted by us and can be passed straight back as `image_url` to any generation endpoint. Asynchronous: returns a queued run — poll `GET /v1/runs/{id}`. Typically 5–20 seconds.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Body Fields

url string required

The page to capture, e.g. `https://example.com`. A bare domain is accepted (https is assumed).

viewports array optional

Up to 4 entries of `{ "name": "desktop", "width": 1440, "height": 900, "mobile": false }`. Width 200–2560, height 200–20000; `mobile: true` emulates a phone (touch + mobile UA), not just a narrow window. Names are slugged and must be unique — they name the shot in the output.

Default: desktop 1440x900 and mobile 390x844

full_page boolean optional

Capture the whole scrollable page instead of the viewport box. Clipped at 8000 CSS pixels so an infinite-scroll page cannot produce an unbounded image.

Default: false

curl Example

curl https://api.genfire.ai/v1/captures \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://example.com",
  "viewports": [
    {
      "name": "desktop",
      "width": 1440,
      "height": 900
    }
  ],
  "full_page": true
}'

Response Example

{
  "id": "run_abc123",
  "object": "run",
  "status": "queued",
  "capability": "website_capture",
  "endpoint": "captures.create",
  "created_at": "2026-09-12T12:00:00.000Z"
}

Request Example Body

{
  "url": "https://example.com",
  "viewports": [
    {
      "name": "desktop",
      "width": 1440,
      "height": 900
    }
  ],
  "full_page": true
}

Notes

  • Requires the `images:write` scope and an `Idempotency-Key` header.
  • Free — website capture never charges credits.
  • The completed run output carries `url`, `final_url` (where redirects landed), `captured_at`, `title` and `shots[]` — each shot with `name`, `width`, `height`, `full_page`, `url` and `bytes`.
  • Every navigation is checked against the outbound-network guard BEFORE the browser loads it and again on the URL it landed on, so a site that redirects to a private address is refused rather than photographed.
  • A run that captured nothing FAILS with a typed code: `capture_unavailable` (no browser on that instance), `navigation_failed` (the page would not load), `unsafe_outbound_url` (we refuse to reach that target) or `unresolved_host` (DNS returned nothing for the hostname, so it is a dead or misspelled name rather than a blocked one). A partial capture completes with the shots it got plus `output.partial_failure`.
  • Cookie-consent banners are hidden before the shot is taken — hidden, not accepted, so nothing is consented to on your behalf.
  • `project_id` is NOT accepted — a capture is stored as run output rather than as an `images` document a project can hold, so naming one is 400 `project_filing_unsupported`. The shot URLs stay on the run and can be passed to any generation endpoint as `image_url`.

Reference

Moodboards

GET /moodboards
moodboards:read

List your moodboards

Returns the moodboards owned by the authenticated account — each a saved aesthetic: its images, the analyzed style profile (taste profile, keywords, avoids, measured palette, exemplar image ids, composed prompt fragment), and any free-text guidelines. Boards hidden in the dashboard are omitted. Boards are created and edited in the Genfire dashboard; the API consumes them via `moodboard_id` on `POST /v1/images/generations`. Boards shared with you by a team are not listed here but remain usable by id.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/moodboards \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "mb_abc123",
      "object": "moodboard",
      "name": "Analog Grain",
      "image_count": 18,
      "images": [
        {
          "id": "img_1",
          "url": "https://storage.googleapis.com/...",
          "thumbnail_url": "https://storage.googleapis.com/...",
          "source": "upload"
        }
      ],
      "analysis": {
        "status": "ready",
        "taste_profile": "Sun-bleached 35mm film photography with soft grain...",
        "keywords": [
          "35mm film",
          "soft grain",
          "muted pastels"
        ],
        "avoids": [
          "digital sharpness",
          "HDR"
        ],
        "palette": {
          "hexes": [
            "#D9C7B2",
            "#8A9B8E"
          ],
          "primary": "#D9C7B2",
          "accent": "#8A9B8E"
        },
        "exemplar_image_ids": [
          "img_1"
        ],
        "prompt_fragment": "In the exact visual style of this moodboard: ..."
      },
      "guidelines": "Always keep skin tones warm.",
      "cover_image_id": "img_1",
      "created_at": "2026-08-10T10:30:00.000Z",
      "updated_at": "2026-08-12T09:12:00.000Z"
    }
  ]
}
GET /moodboards/{moodboardId}
moodboards:read

Get a moodboard

Returns one moodboard: its images and the analyzed style profile. Team members a board is shared with can read (and generate with) it; only the owner can edit it in the dashboard. While analysis is running, `analysis.status` is `analyzing` and the style fields fill in when it completes — the board still grounds generations meanwhile (guidelines and reference images apply even before the analysis is ready).

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

moodboardId path required

Moodboard id from `GET /v1/moodboards` or a forked preset.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/moodboards/{moodboardId} \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "id": "mb_abc123",
  "object": "moodboard",
  "name": "Analog Grain",
  "image_count": 18,
  "analysis": {
    "status": "ready",
    "keywords": [
      "35mm film",
      "soft grain"
    ],
    "prompt_fragment": "In the exact visual style of this moodboard: ..."
  },
  "guidelines": null,
  "cover_image_id": "img_1"
}

Notes

  • 404 if the moodboard does not exist, or is neither owned by nor shared with the authenticated account.
GET /moodboards/presets
moodboards:read

List house-curated preset moodboards

Returns the house-curated preset boards — ready-made aesthetics anyone can fork. Every preset ships with a completed analysis, so a fork is instantly usable in generation with no re-analysis.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/moodboards/presets \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "preset_abc123",
      "object": "moodboard_preset",
      "name": "Editorial Noir",
      "order": 1,
      "image_count": 12,
      "analysis": {
        "status": "ready",
        "keywords": [
          "high-contrast monochrome"
        ]
      },
      "guidelines": "Keep highlights blown, shadows crushed."
    }
  ]
}
POST /moodboards/presets/{presetId}/fork
moodboards:read

Fork a preset into your own moodboards

Copies a house preset into your own moodboards — the copy is fully yours afterwards and lands with a ready analysis, immediately usable as `moodboard_id` in generation. Free and synchronous; nothing is fetched from third parties.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

presetId path required

Preset id from `GET /v1/moodboards/presets`.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/moodboards/presets/{presetId}/fork \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "id": "mb_new456",
  "object": "moodboard",
  "name": "Editorial Noir",
  "image_count": 12,
  "analysis": {
    "status": "ready"
  }
}

Notes

  • Guarded by `moodboards:read` — the API has no moodboards:write scope because board authoring stays in the dashboard; forking only copies house content into your own account.
  • Free — forking a preset never charges credits.
  • Retrying the call creates another copy (forks are not idempotent); delete duplicates in the dashboard.
  • 400 `fork_preset_failed` when your account is at the moodboard limit (200 boards) — delete a board and retry.

Reference

Teams

GET /teams
teams:read

List your workspaces

Returns the teams (workspaces) the authenticated account belongs to, each with your role on it, the member count and the shared CREDIT POOL balance. Agencies typically keep one workspace per client. Pass a workspace id as `team_id` on a generating request to bill that pool instead of your personal balance. Teams are created, invited to and configured in the Genfire dashboard; the API consumes them.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/teams \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "team_abc123",
      "object": "team",
      "name": "Acme Skin",
      "role": "admin",
      "is_owner": false,
      "member_count": 4,
      "seat_limit": 10,
      "pool": {
        "balance": 12400,
        "currency": "credits"
      },
      "created_at": "2026-06-01T09:00:00.000Z",
      "updated_at": "2026-08-18T10:00:00.000Z"
    }
  ]
}

Notes

  • There is no stateful "select workspace" call. `team_id` is named on each request that spends, so the payer is explicit at the point of payment — a remembered selection going stale would quietly charge a different client.
GET /teams/{teamId}
teams:read

Get one workspace

Returns one workspace with its pool balance plus YOUR month-to-date usage and monthly cap on it. Both numbers matter before a run: a member can be well under the pool balance and still blocked by their own cap.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

teamId path required

Team id from `GET /v1/teams`.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/teams/{teamId} \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "id": "team_abc123",
  "object": "team",
  "name": "Acme Skin",
  "role": "admin",
  "is_owner": false,
  "member_count": 4,
  "pool": {
    "balance": 12400,
    "currency": "credits"
  },
  "my_usage_this_month": 820,
  "my_cap": 5000
}

Notes

  • 404 when the team does not exist OR you are not a member — the two are indistinguishable on purpose, so ids cannot be probed.

Reference

Video

POST /videos/analyses
videos:write

Analyse a video scene by scene

Understand what is VISUALLY in a video. Frames are sampled across the whole clip and read by a vision model, returning a summary, a scene list (start/end seconds, description, shot type, subjects, on-screen text, camera movement) and the structural beats in order. This is not transcription — `POST /v1/audio/transcriptions` reads what is SAID, this reads what is SHOWN, and a silent product film transcribes to nothing while analysing fully. Use it to break a competitor ad down, turn a reference video into a shot list, or answer "what happens at 0:14?". Asynchronous: returns a queued run — poll `GET /v1/runs/{id}`. Typically 1–3 minutes, longer for a YouTube source.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Body Fields

video_url string optional

The video to analyse. Provide exactly one of video_url or youtube_url.

youtube_url string optional

A YouTube link to analyse. Provide exactly one of video_url or youtube_url.

depth string optional

`summary` (3–6 major movements), `scenes` (one entry per meaningful beat), or `shot-list` (one entry per camera setup, for a shot-for-shot recreation).

Default: scenes

team_id string optional

Bill a workspace pool instead of your balance (`GET /v1/teams`). You must be a member with a spending role.

curl Example

curl https://api.genfire.ai/v1/videos/analyses \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "youtube_url": "https://www.youtube.com/watch?v=example",
  "depth": "shot-list"
}'

Response Example

{
  "id": "run_abc123",
  "object": "run",
  "status": "queued",
  "capability": "video_analysis",
  "endpoint": "videos.analyses.create",
  "created_at": "2026-08-19T12:00:00.000Z"
}

Request Example Body

{
  "youtube_url": "https://www.youtube.com/watch?v=example",
  "depth": "shot-list"
}

Notes

  • The completed run output carries `summary`, `scenes[]`, `hooks_and_beats[]`, `duration_seconds` and `frames_analyzed`.
  • Scene timestamps are clamped to the measured video length — a model that runs past the end is truncated rather than returned as a time nothing can seek to.
  • Frames are samples, so a scene boundary falls somewhere between two of them; treat the times as close, not frame-exact.
  • Videos are capped at 500MB. A `video_url` is validated against the outbound-network guard, including every redirect hop.

Reference

Generation

POST /scenes/blockouts
images:write

Describe a 3D set, get an editable blockout spec

Turn a sentence into 3D SCENE DATA a host app can build: primitives with positions, sizes and colours, lights, a camera — or a keyframed camera move. Nothing here is a rendered picture or an opaque mesh; the Genfire for Blender add-on builds the spec as real, editable objects in the open file, and hands the same spec back with a change request (`mode: reblock`) so a layout can be fixed rather than rebuilt. Three modes: `blockout` (prompt → spec), `reblock` (prompt + the previous `spec` → the complete updated spec, unchanged object names preserved), `camera` (prompt + scene context → keyframes). Pass `scene` — a summary of the open file — so the model places things relative to what is already there. Synchronous and not billed.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

prompt string required

The set to build, the change to make, or the camera move to key. Up to 4000 characters.

mode string optional

`blockout`, `reblock` or `camera`.

Default: blockout

scene object optional

Optional context from the open file: `summary`, `objects[]` ({name, location, dimensions, primitive}), `bounds` ({min, max}), `camera` ({name, location, rotation_deg, lens_mm}), `frame_start`, `frame_end`, `fps`, `cursor`.

spec object optional

Required for `reblock`: the `spec` a previous call returned (with any edits the host made).

max_objects integer optional

Soft cap on primitives in a blockout (1–120).

Default: 60

curl Example

curl https://api.genfire.ai/v1/scenes/blockouts \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "mode": "blockout",
  "prompt": "A low-poly forest clearing with a lake, tall pines around the edge and a campfire in the middle",
  "scene": {
    "frame_start": 1,
    "frame_end": 120,
    "fps": 24
  }
}'

Response Example

{
  "object": "scene_blockout",
  "mode": "blockout",
  "model": "claude-sonnet-5",
  "spec": {
    "kind": "blockout",
    "name": "Forest clearing",
    "units": "meters",
    "objects": [
      {
        "name": "Ground",
        "primitive": "plane",
        "location": [
          0,
          0,
          0
        ],
        "rotation_deg": [
          0,
          0,
          0
        ],
        "dimensions": [
          40,
          40,
          0.01
        ],
        "color": [
          0.3,
          0.42,
          0.25
        ],
        "role": "ground"
      },
      {
        "name": "Lake",
        "primitive": "cylinder",
        "location": [
          6,
          4,
          0.01
        ],
        "rotation_deg": [
          0,
          0,
          0
        ],
        "dimensions": [
          10,
          8,
          0.02
        ],
        "color": [
          0.2,
          0.4,
          0.7
        ]
      }
    ],
    "lights": [
      {
        "name": "Sun",
        "type": "SUN",
        "location": [
          0,
          0,
          10
        ],
        "rotation_deg": [
          50,
          0,
          35
        ],
        "energy": 3,
        "color": [
          1,
          0.95,
          0.85
        ]
      }
    ],
    "camera": {
      "name": "Camera",
      "location": [
        14,
        -16,
        6
      ],
      "look_at": [
        0,
        0,
        1
      ],
      "lens_mm": 35
    },
    "world": {
      "color": [
        0.55,
        0.7,
        0.9
      ],
      "strength": 1
    },
    "notes": "Pines ring the clearing; the campfire is the focal subject."
  }
}

Request Example Body

{
  "mode": "blockout",
  "prompt": "A low-poly forest clearing with a lake, tall pines around the edge and a campfire in the middle",
  "scene": {
    "frame_start": 1,
    "frame_end": 120,
    "fps": 24
  }
}

Notes

  • Coordinates are Blender-style: Z up, metres, `rotation_deg` is Euler XYZ. `dimensions` are full extents (a cylinder's x/y are its diameter and z its height).
  • A `camera` spec returns `kind: camera_move` with `frame_start`, `frame_end`, `interpolation`, `lens_mm` and `keyframes[]` ({frame, location, look_at | rotation_deg, lens_mm}).
  • Every value is normalised and clamped before it is returned (finite numbers, ≤120 objects, ≤8 lights, ≤60 keyframes, colours 0–1), so a host can build it without re-validating shape.
  • 502 `model_output_invalid` when the model did not answer with a spec — reword and retry.
POST /images/generations
images:write

Generate images

Create one or more images using a public image model alias. This endpoint usually completes synchronously and returns a completed run.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Body Fields

prompt string required

The image prompt. May include @<handle> mentions when paired with a `mentions` array.

model string optional

Optional image model alias from `/models`. Models with an internal edit variant accept `mentions`, `image_url`, and `image_urls` for influencer-conditioned or multi-image edits.

Default: image.nano_banana_2_1

aspect_ratio string optional

Image aspect ratio.

Default: 1:1

count integer optional

Number of images to generate, from 1 to 4.

Default: 1

image_url string optional

Optional single source image URL. When provided with an edit-capable model, the request is routed through the model's edit variant. Use `image_urls` for multi-image edits.

image_urls array optional

Optional list of up to 14 source image URLs for a multi-image edit. Supported by GPT Image 2 / 2.5, Seedream, Qwen Image 2 and Nano Banana; Grok uses at most the first 3. `image.ideogram_v4_5` reads the first image as the SOURCE to edit and up to 4 more as references (3 when `mask_url` is sent); any beyond that are ignored. Routes through the model's edit variant.

mask_url string optional

Optional MASKED INPAINT: an absolute http(s) URL to a mask image (white = repaint, black = keep) the same size as the source. Only the white region is regenerated; everything else is preserved. Requires `image_url` (or `image_urls`) — a mask alone is rejected with 400 `mask_requires_source_image`. Only accepted by models whose `capabilities.masked_inpaint` is true on `GET /v1/models` (image.gpt_image_2, image.gpt_image_25_flare, image.gpt_image_25_sunburst, image.genfire_v1, image.nano_banana_2, image.nano_banana_2_1, image.nano_banana_pro, image.ideogram_v4_5, image.z_image_turbo, image.z_image_tiling); any other model returns 400 `model_lacks_mask_support` rather than silently ignoring the mask and billing a full-frame rewrite. White = repaint on EVERY model: Ideogram's own API reads masks the other way round, and Genfire inverts yours server-side, so never pre-invert one. Upload the mask with `POST /v1/uploads` and pass the returned `asset_url`; data: URIs are rejected with 400 `invalid_mask_url`, and the URL is checked against the same SSRF guard as every other caller-supplied URL (400 `unsafe_outbound_url`).

strength number optional

Z-Image image-to-image only (`image.z_image_turbo`, `image.z_image_tiling` with `image_url`): how far the result may move from the source, 0-1 (default 0.6; inpaint defaults to 1). Z-Image image-to-image is NOT instruction editing — the prompt describes the whole result. Any other model returns 400 `strength_unsupported`.

negative_prompt string optional

`image.z_image_base` only: what to steer away from. Turbo is guidance-distilled and cannot read one; any other model returns 400 `negative_prompt_unsupported`.

control_mode string optional

`image.z_image_controlnet` only: what is extracted from `image_url` to guide structure — `canny` (edges, default), `depth`, `pose`, or `none` when the image is already a control map. `image_url` is required on this model (400 `control_image_required`).

control_scale number optional

`image.z_image_controlnet` only: how tightly the result follows the control image, 0-1 (default 0.75).

tiling_mode string optional

`image.z_image_tiling` only: which edges wrap seamlessly — `both` (default), `horizontal` or `vertical`.

mentions array optional

Optional `[{ handle, influencer_id }]` to inject a trained influencer character into the image. The model is auto-switched to its edit variant and the influencer's reference photos are added as conditioning. Currently a single mention per request is supported.

brand_id string optional

Optional id of one of your brands (`GET /v1/brands`). Grounds the generation server-side: the brand's real product images are attached as edit sources and its colors/style/voice are prepended to the prompt. Invalid or unowned id → 404 `invalid_brand_id`.

moodboard_id string optional

Optional id of one of your moodboards (`GET /v1/moodboards`, or a board shared with you). Requires the `moodboards:read` scope in addition to `images:write` (403 `insufficient_scope` otherwise — the board's style text echoes back through the run record). Styles the generation server-side: the board's composed style fragment is prepended to the prompt and its exemplar images are attached as edit sources. Composes with `brand_id` — the brand prefix leads and brand references take the reference slots first. Unknown or inaccessible id → 404 `invalid_moodboard_id`; other resolution failures → 400 `invalid_moodboard_id`. On models without an internal edit variant the style applies as prompt text only (no exemplar attachment). The run's `input` records `moodboard_id` and `moodboard_strength`, so `GET /runs/{id}` shows what styled the run.

moodboard_strength string optional

How hard the moodboard steers: `subtle` (prompt fragment only, no reference images), `balanced` (fragment + up to 3 exemplars), or `strong` (fragment + as many exemplars as fit under the 14-image cap). Any other value → 400 `invalid_moodboard_strength`. Only meaningful with `moodboard_id`.

Default: balanced

project_id string optional

File this generation's output into one of your PROJECTS (`GET /v1/projects`) the moment the run completes — the same thing the Video Studio does when you have that project open. Access is checked before the run is created, so an unknown id or one you cannot edit is a 404 `project_not_found` / 403 `project_forbidden` at request time rather than a silently unfiled result. Filing itself is best-effort and never fails a run you have already paid for. The run echoes it back as `project_id`.

team_id string optional

Bill this generation to a WORKSPACE (team) credit pool instead of your balance (`GET /v1/teams`). Membership and a spending role are asserted before the run is created — 404 `invalid_team_id` when you are not a member, 403 `team_role_cannot_spend` when your role cannot spend. Requires the `teams:read` scope. The run echoes it back as `team_id`.

marketing_template_id string optional

Optional id of a Marketing Studio ad template (`GET /v1/marketing/templates`). Composes the request server-side from the template RECIPE: the recipe prompt has its `{{product}}` / `{{avatar}}` tokens replaced with the positions your slot media is actually sent in, the slot media is emitted in the recipe's own `slot_order` AHEAD of any brand/moodboard references, and the template's model, aspect ratio, resolution and count are applied unless you pass your own. Fill the fields the template lists under `recipe.required_inputs`; a missing one → 400 `marketing_slot_missing`. Passing a template for the other media type → 400 `marketing_template_mode_mismatch`. Unknown id → 404 `marketing_template_not_found`. A template whose model has no /v1 alias → 400 `template_model_unavailable` (pass an explicit `model` to run it anyway). A `prompt` you send alongside is ADDITIVE — it says what to CHANGE, so do NOT re-send the recipe prompt.

marketing_format_id string optional

Optional GENRE (`GET /v1/marketing/formats`) — unboxing, tutorial, before-after, star review, comparison and so on. Fragments compose in the order genre → hook → setting, so the genre defines the piece, the hook claims its opening beat, and the setting relocates whatever those produced. WITH `marketing_template_id` the recipe is the base and the three fragments follow it, with your own `prompt` last as a change note. WITHOUT a template your `prompt` IS the base subject description and the fragments are directives applied to it, so they follow your prompt instead. Works WITHOUT a template. Unknown id → 400 `invalid_marketing_format_id`; a genre that does not apply to this media type → 400 `format_mode_mismatch`.

marketing_hook_id string optional

Optional OPENING MECHANIC (`GET /v1/marketing/hooks`) — a physical event in the first two seconds, not a copy angle: the product flying at the creator, an indoor blizzard, the camera being knocked, a held silence. `stunt` or `subtle`. Independent of the format, so any hook can open any genre. Works WITHOUT a template. Unknown id → 400 `invalid_marketing_hook_id`; a hook that does not apply to this media type → 400 `hook_mode_mismatch`.

marketing_setting_id string optional

Optional ENVIRONMENT (`GET /v1/marketing/settings`) — `realistic` (marble kitchen, gym floor, golden hour) or `unrealistic` (airplane wing, volcano rim, shrunk to doll size). Written as a relocation directive so it overrides the scene a template describes. Works without a template. Unknown id → 400 `invalid_marketing_setting_id`.

product_image_url string optional

Fills the template's PRODUCT slot. Takes precedence over `product_id`.

avatar_image_url string optional

Fills the template's AVATAR slot — who appears in the ad. Takes precedence over `avatar_id`. Rejected with 400 `avatar_not_supported_by_template` on a start-frame video template, which animates the product photo itself and has no avatar slot.

product_id string optional

Fills the PRODUCT slot from your stored brand products (`GET /v1/marketing/products`) — no re-upload. Unknown or imageless id → 400 `invalid_product_id`.

avatar_id string optional

Fills the AVATAR slot from your trained influencers or the house avatar library (`GET /v1/marketing/avatars`). Accepts the prefixed id (`influencer:abc`, `preset:xyz`), a bare influencer id, or an @handle. Unknown id → 400 `invalid_avatar_id`.

quality string optional

Quality tier. image.gpt_image_2 / image.gpt_image_25_sunburst / image.gpt_image_25_flare / image.genfire_v1: low, medium, high, auto — cost low=1×, medium=6×, high=22×. image.grok_imagine_2: low or medium only, priced as separate base rates (low is cheaper); anything else is rejected. image.ideogram_v4_5: low, medium or high (no auto), each its own base rate; anything else is rejected.

Default: medium

resolution string optional

Output resolution: 1K, 2K, 4K. image.grok_imagine_pro and image.grok_imagine_2 accept 1K/2K on BOTH the text-to-image and edit paths (4K is rejected). image.nano_banana_2, image.nano_banana_2_1 and image.nano_banana_pro accept it on BOTH paths. Affects cost: 1K=1×, 2K=1.5×, 4K=3× — except image.ideogram_v4_5, which takes 1K or 2K (default 2K) at the SAME price and rejects 4K; its masked and Precise (`edit_precision: high`) edits keep the source image's size.

edit_precision string optional

`image.ideogram_v4_5` edits only: `regular` (default) or `high` — Precise Edit, which restores every pixel the edit did not change instead of re-rendering the whole frame. Same price as a regular edit. Requires `image_url` (or `image_urls`) — without one it is 400 `edit_precision_requires_source_image`; on any other model it is 400 `edit_precision_unsupported` rather than silently ignored; any other value is 400 `invalid_request`.

async boolean optional

When true, returns the QUEUED run immediately and generates in the background — poll `GET /runs/{id}` (or subscribe to webhooks) for completion, exactly like video. Credits are pre-checked up front (402 on insufficient balance). Defaults to false (the request holds until the images are ready). The same request dedupes to the same run whether called sync or async.

Default: false

quote_token string optional

A signed price quote from the matching estimate endpoint, binding this submit to the price you were shown. May also be sent as the `X-Genfire-Quote` header. The token carries the quoted inputs, so it is only spendable on the request it was issued for: 409 `quote_expired` past its 10-minute life, 409 `quote_mismatch` when the inputs or the price no longer match, 401 `quote_invalid` when the signature does not verify. Every 409 carries a FRESH quote in the problem body so you can resubmit in one step.

curl Example

curl https://api.genfire.ai/v1/images/generations \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "prompt": "Studio product photo of a matte black water bottle on dark stone",
  "model": "image.nano_banana_2",
  "aspect_ratio": "1:1",
  "count": 1
}'

Response Example

{
  "id": "run_img_123",
  "object": "run",
  "status": "completed",
  "capability": "image_generation",
  "endpoint": "images.generations.create",
  "model": "image.nano_banana_2",
  "request_id": "run_img_123",
  "input_summary": {
    "prompt": "Studio product photo of a matte black water bottle",
    "model": "image.nano_banana_2",
    "aspect_ratio": "1:1",
    "count": 1
  },
  "output": {
    "images": [
      {
        "url": "https://cdn.genfire.ai/output/image-001.png"
      }
    ],
    "output_url": "https://cdn.genfire.ai/output/image-001.png"
  },
  "usage": {
    "credits": 8
  },
  "error": null,
  "resource_id": null,
  "provider_request_id": null,
  "team_id": null,
  "project_id": null,
  "created_at": "2026-03-25T12:00:00.000Z",
  "updated_at": "2026-03-25T12:00:02.000Z",
  "completed_at": "2026-03-25T12:00:02.000Z"
}

Request Example Body

{
  "prompt": "Studio product photo of a matte black water bottle on dark stone",
  "model": "image.nano_banana_2",
  "aspect_ratio": "1:1",
  "count": 1
}

Notes

  • Masked inpainting: pass `image_url` + `mask_url` + a prompt describing the change. The mask is a black-and-white PNG matching the source dimensions — white marks the pixels to repaint. Filter your model picker on `capabilities.masked_inpaint` from `GET /v1/models`.
  • When `mentions` is supplied: the model must have an internal edit variant (most do — see `/models`). The server resolves the influencer, replaces `@<handle>` in the prompt with `the character`, appends an identity directive, and adds up to four reference images as conditioning.
  • Influencers must be in status `ready`. Drafts and archived ones cannot be referenced.
POST /videos/generations
videos:write

Generate videos

Create a video run. Routing is automatic from the inputs you supply: prompt only → text-to-video, +image_url → image-to-video, +reference_image_urls (or Wan 3.0's file_url / web_url) → reference-to-video, +source_video_url → video-edit/extend, +image_url +source_video_url → motion-control, +first_frame_url +last_frame_url → first-last-frame interpolation. Each video model declares which combinations it supports under `capabilities` on `GET /models`.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Body Fields

prompt string required

The video prompt. Three exceptions: `video.hailuo_03_max_3d` (H3 Max 3D to Video), where it is optional scene intent (≤2,000 characters) — omit it or send an empty string and the 3D render alone drives the clip; `video.hailuo_03_max_insert` (H3 Max Insert), where it describes the NEW scene and may be omitted when `reference_image_urls` / `reference_video_urls` guide it instead — with neither it is 400 `insert_guidance_required`; and `video.hailuo_03_max_recast` (H3 Max Recast), where it is optional (≤2,000 characters) and only says who becomes whom, or what else to keep — the cast photos alone are enough.

model string optional

Optional video model alias from `/models`.

Default: video.veo_3_1

aspect_ratio string optional

Video aspect ratio. Omitted, it is 16:9 — with one exception: `video.seedance_2_5` with EXACTLY ONE `reference_video_urls` clip and `task` `reference` (or omitted) or `editing` follows the clip (`auto`). Send a ratio to override it — on a one-clip `reference` run a ratio other than `auto` or the clip's own also stops the LENGTH following the clip (an omitted duration is then 5s).

Default: 16:9

duration integer optional

Target duration in seconds. Omitted, it is 5. On `video.seedance_2_5`, `"auto"` means the same as omitting it — never a length the model picks: with EXACTLY ONE `reference_video_urls` clip, `task` `reference` (or omitted) and `aspect_ratio` omitted, `auto` or the clip's own ratio, the run follows the clip's own length (the trimmed window when trimmed) — whole seconds, rounded UP, 4–30 — and bills on that length, as an edit does; in every other case it is 5s. Send a number to override it.

Default: 5

resolution string optional

Output resolution (e.g. 480p, 720p, 1080p, 4k). Supported values and pricing are per-model — see `resolutions` under `/models`. Higher resolutions cost more credits.

image_url string optional

Source image for image-to-video, or character image for motion-control.

source_video_url string optional

Source video for video-edit, video-extend, or motion-control modes. REQUIRED on `video.hailuo_03_max_extend` (the clip to continue, 1.625-60s, ≤50 MB), `video.hailuo_03_max_3d` (the 3D render, ≤15s), `video.hailuo_03_max_insert` (the clip to cut into, ≤60s, ≤50 MB) and `video.hailuo_03_max_recast` (the clip to recast, 5-30s, no single shot over 15s) — 400 `source_video_url_required` without it. All four are measured server-side before the run is created: a clip outside those bounds is 400 `source_video_too_short` / `source_video_too_long`, a clip over 50 MB (Extend / Insert; also `video.flux_3_edit`) is 400 `source_video_too_large`, and an unreadable 3D render or Recast source is 422 `source_video_unreadable` (its length is the price).

reference_image_urls array optional

Reference image URLs for reference-to-video mode. Up to 9 on most models; `video.seedance_2_5` accepts up to 30, and the `video.wan_3*` tiers and both Gemini Omni Flash tiers accept up to 10 (see `limits.max_reference_images` on GET /models). On `video.hailuo_03_max_recast` they are the CAST and REQUIRED: 1-4 photos, one per new person, mapped left to right onto the main people in `source_video_url` — none is 400 `reference_image_urls_required`, a 5th is 400 `invalid_reference_image_urls`. On `video.seedance_2_5` an `image_url` sent alongside any reference media (`reference_image_urls`, `reference_video_urls` or `reference_audio_urls`) is not animated as a start frame: it is appended AFTER these images and becomes the next `@ImageN` — cite it that way (only a pool already holding 30 images makes that a 400). To animate a frame, send `image_url` with no reference media.

file_url string optional

Wan 3.0 only (`video.wan_3`, `video.wan_3_prime`). URL of a document to base the video on — the model reads it and builds the shot from its contents. Selects reference-to-video on its own, and turns on Wan's reasoning pass automatically. Sending it to any other model returns 400 `unsupported_source_url`.

web_url string optional

Wan 3.0 only (`video.wan_3`, `video.wan_3_prime`). URL of a PUBLIC webpage to base the video on — pages behind a login cannot be read. Selects reference-to-video on its own, and turns on Wan's reasoning pass automatically. Sending it to any other model returns 400 `unsupported_source_url`.

reference_video_urls array optional

Seedance, Hailuo 03, Wan 3.0 and Gemini Omni Flash 1.1 only (3 on `video.gemini_omni_flash_v11`; the older `video.gemini_omni_flash`, `video.hailuo_03_max_turbo` and `video.hailuo_03_max_styles` accept none). Up to 3 reference clips on `video.hailuo_03`, `video.hailuo_03_max` and the `video.seedance_2_0*` models (cited as `Video 1`…); up to 10 on `video.seedance_2_5` (cited as `@Video1`…; each 2–30s after any trim, 30s COMBINED across the pool, duplicate URLs de-duplicated; `task` `editing` and `extension` take EXACTLY ONE clip and an edit clip must be at least 4s); up to 5 on `video.wan_3` / `video.wan_3_prime`, 15s TOTAL across the pool and at least 16 fps each (cited as `Video 1`…); up to 3 on `video.hailuo_03_max_insert`, where they guide the inserted scene — 15s TOTAL across the pool, measured server-side (400 `reference_video_pool_too_long` over it). Each clip is roughly 2–30s.

reference_video_trims array optional

Optional per-clip TIME WINDOWS for `reference_video_urls` — "use seconds 3–8 of the second clip". Each entry is `{ index, start, end }` (or `{ url, start, end }`, or `duration` instead of `end`; seconds, `index` is 0-based into `reference_video_urls`). Only the window is sent to the model and — where clip length is billed — billed. Windows must fit the model's per-clip cap: 3s on `video.gemini_omni_flash_v11`, 15s on `video.hailuo_03*` and `video.wan_3*` (15s TOTAL across the Wan pool), 30s on Seedance; a longer window is a 400 `invalid_reference_video_trims`. The cut happens server-side with audio kept.

reference_audio_urls array optional

Seedance, Hailuo 03 and Wan 3.0 only. Up to 3 reference audio clips on `video.hailuo_03` and the `video.seedance_2_0*` models, up to 10 on `video.seedance_2_5` (each 2–30s and at most 15 MB, 30s COMBINED), up to 5 (15s total) on `video.wan_3` / `video.wan_3_prime`. Cite them positionally in the prompt (`Audio 1` on Hailuo 03, `@Audio1` on Seedance) — this is how you give an on-camera character a consistent voice ("the woman in Image 1 speaks with the voice in Audio 1"). Cannot be the only reference: pair it with at least one reference image or video.

first_frame_url string optional

First-frame image for first-last-frame interpolation. Must be paired with last_frame_url.

last_frame_url string optional

Last-frame image for first-last-frame interpolation. Must be paired with first_frame_url.

generate_audio boolean optional

Whether to include generated audio when supported. Exception: on `video.seedance_2_5` with EXACTLY ONE `reference_video_urls` clip and `task` `reference` (or omitted) or `editing`, sending neither `generate_audio` nor `keep_source_audio` keeps the CLIP'S OWN sound instead (`keep_source_audio` defaults to true there) — unless `reference_audio_urls` is sent or the clip has no audio track, where the soundtrack is generated as usual. Send `generate_audio: true` for a generated soundtrack, or `false` for silence.

Default: true

bitrate_mode string optional

Output ENCODE quality: `standard` or `high`. `high` requests a larger, higher-quality file at no extra credit cost. Declared by `video.seedance_2_0` and `video.seedance_2_0_fast`; on every other model (`video.seedance_2_5`, `video.seedance_2_0_mini` and every non-Seedance model — `capabilities.bitrate_mode` is false) it has no effect and is ignored, whichever value you send. A value other than `standard` or `high` is 400 `invalid_bitrate_mode`. Filter on `capabilities.bitrate_mode` at GET /v1/models.

Default: standard

task string optional

GENFIRE GENJUDO — `video.seedance_2_5` only (`capabilities.video_task` at GET /v1/models); any other model returns 400 `unsupported_task`. `reference` = MOTION TRANSFER: the clip in `reference_video_urls` supplies the motion, the images in `reference_image_urls` supply who performs it. With EXACTLY ONE clip it MATCHES THE CLIP by default: an omitted `aspect_ratio` is `auto`, an omitted (or `auto`) `duration` is the clip's own length (billed on the measured clip) unless you send a ratio other than `auto` or the clip's own, and with neither `generate_audio` nor `keep_source_audio` sent the clip's own sound is kept (not when `reference_audio_urls` is sent or the clip has no audio track) — send any of them to override. `editing` = VIDEO EDIT: the clip itself is re-lit / swapped / cleaned up, and the model coerces BOTH `aspect_ratio` and `duration` to auto (the output follows the source, so sending either is a no-op); the clip's own sound is kept by default the same way. `extension` = continue the clip past its last frame; `aspect_ratio` is coerced to auto, `duration` is yours (5s when omitted), no other default applies and `keep_source_audio` has no effect (ignored — the new seconds have no source sound). `editing` and `extension` take EXACTLY ONE `reference_video_urls` clip — none is 400 `task_requires_reference_video`, a second is a 400 too — and an edit clip must be at least 4s. Omit `task` for the model default (reference). Pre-written prompts for both modes: GET /v1/videos/genjudo/presets.

keep_source_audio boolean optional

GENFIRE GENJUDO — `video.seedance_2_5` with a `reference_video_urls` clip, task `reference` (or omitted) or `editing`; ignored on `extension` (the new seconds have no source sound) and when no clip is sent. The clip is sent to the model SILENT and its own soundtrack (the same trim window) is laid back under the finished render, so the result keeps the original sound and no generated audio is written (`generate_audio` is forced false). DEFAULTS TO TRUE with exactly one clip when you send neither `keep_source_audio` nor `generate_audio` — except when `reference_audio_urls` is sent or the clip has no audio track; send `false` (or `generate_audio: true`) for a generated soundtrack instead. Any other model returns 400 `unsupported_genjudo_option`.

follow_source boolean optional

GENFIRE GENJUDO "Match the clip" — `video.seedance_2_5` task `reference` with a clip and `aspect_ratio` omitted, `auto` or the clip's own ratio: the output frame and length follow the clip, which lets a motion-transfer prompt that reads like an edit ("replace the person in @Video1…") run instead of being refused. An explicit `follow_source: true` REPLACES any `duration` you send with the clip's own length — whole seconds, rounded UP, 4–30 — and that is what bills; an `aspect_ratio` other than `auto` or the clip's own stops it following. A one-clip `reference` run that sends neither `aspect_ratio` nor `duration` already matches the clip without it. Dropped silently where it does not apply; any non-Genjudo model returns 400 `unsupported_genjudo_option`.

Default: false

draft boolean optional

Seedance 2.5 DRAFT — `video.seedance_2_5` only (`capabilities.draft` at GET /v1/models); any other model returns 400 `unsupported_draft` (Flux 3's draft is its own model, `video.flux_3_draft`). `true` renders a 480p PREVIEW billed at the 480p price — on text-, image- and reference-to-video, `task` included — and `resolution` is ignored (always 480p). Happy with it? `POST /v1/videos/generations/{id}/final` with the draft's run id re-renders the SAME shot (prompt, references, duration, aspect ratio, audio) as a native 1080p video, priced at what a direct 1080p render of this request costs; finals can be made for 7 days from the draft's creation, as many times as you like. Not a boolean → 400 `invalid_draft`; 503 `draft_unavailable` when the draft lane is down (send it without `draft` to render normally).

Default: false

camera_path string optional

H3 Max Multi Angle only (`video.hailuo_03_max_multi_angle`): a preset camera move around the `image_url` subject — `orbit` (one full 360° turn), `orbit_reverse`, `half_orbit`, `quarter_orbit`, `dolly_in`, `dolly_out`, `crane_up`, `low_angle`, `top_down`, `arc_push`, `spiral`, `hero_reveal`. Omit for the model default. Any other model returns 400 `unsupported_camera_trajectory`.

camera_trajectory array optional

H3 Max Multi Angle only: explicit camera keyframes instead of `camera_path` — 2 to 12 objects `{ time, azimuth, elevation, distance }`. `time` runs 0 (first frame) to 1 (last); `azimuth` is degrees around the subject, signed and unbounded (360 = one full turn, up to 32 turns of total travel); `elevation` is -90 (below) to 90 (top-down); `distance` is in scene units, 1 = the source framing, smaller is closer. The first pose is held before its time and the last after it. Overrides `camera_path` when both are sent; a malformed list returns 400 `invalid_camera_trajectory`.

video_style string optional

H3 Max Styles only (`video.hailuo_03_max_styles`, `capabilities.video_styles` at GET /v1/models) — and REQUIRED there (400 `video_style_required`): the baked-in look the prompt is rendered in. `vhs` (camcorder tape: scan lines, colour bleed, tracking wear), `retro_toon_70s` (1970s hand-painted cel animation), `low_poly` (faceted, flat-shaded retro 3D), `hand_drawn` (pencil-on-paper animation), `16bit_pixel` (16-bit console pixel art). Any other value is 400 `invalid_video_style`; sending it to any other model is 400 `unsupported_video_style`. The legal values are the model's `limits.video_styles`.

damage_level string optional

H3 Max Styles with `video_style: "vhs"` only: how worn the tape looks — `light`, `medium` or `heavy`. Omit for the model default (medium). Any other value is 400 `invalid_damage_level`; sending it with another style or another model is 400 `unsupported_damage_level`.

Default: medium

extend_output string optional

H3 Max Extend only (`video.hailuo_03_max_extend`): `extended` returns the source clip plus the new footage as one video, `continuation` returns only the new footage. Omit for the default (`extended`). Any other value is 400 `invalid_extend_output`; sending it to any other model is 400 `unsupported_extend_output`. Does not change the price.

Default: extended

max_generated_reference_images integer optional

H3 Max 3D to Video only (`video.hailuo_03_max_3d`): when you send NO `reference_image_urls`, the model generates up to this many appearance references itself (1-8, default 2) and each one is billed, so the cap is part of the quote. Ignored — and not billed — when reference images are sent. Outside 1-8 is 400 `invalid_max_generated_reference_images`; sending it to any other model is 400 `unsupported_max_generated_reference_images`.

Default: 2

insert_start_time number optional

H3 Max Insert only (`video.hailuo_03_max_insert`), and REQUIRED there with `insert_resume_time` (400 `insert_window_required`): the second of the source clip where the NEW scene begins — at least 1.625, at most 60. Seconds into `source_video_url` as supplied (the source is never trimmed first); decimals allowed. A non-number is 400 `invalid_insert_window`; sending it to any other model is 400 `unsupported_insert_window`.

insert_resume_time number optional

H3 Max Insert only, REQUIRED there: the second of the source clip where the ORIGINAL footage picks up again after the new scene — later than `insert_start_time`, at most 60, and before the clip ends (leave some footage after it for the transition). The source is measured before the run exists, so a resume at or past its end is 400 `invalid_insert_window`, as is any window outside those bounds. The span it replaces does not set the new scene's length — `duration` does. Any other model: 400 `unsupported_insert_window`.

color_match boolean optional

H3 Max Insert only: grade the new scene's colour to the source clip so the cut in and out is seamless. Send `false` only for a deliberate look change. Does not change the price. A non-boolean is 400 `invalid_color_match`; sending it to any other model is 400 `unsupported_color_match`.

Default: true

elements array optional

Kling O3 only. Up to 3 structured elements (characters/objects), referenced in the prompt as `@Element1`, `@Element2`, `@Element3`. Passing elements selects the reference-to-video endpoint. Each element is an object: `{ frontal_image_url: string (main view), reference_image_urls?: string[] (1–3 extra angles), video_url?: string (a 3–10s driving clip — at most ONE element may carry a video), voice_id?: string (from POST /videos/voices) }`. Each element must have a frontal_image_url OR a video_url.

multi_prompt array optional

Kling O3 only. Multi-shot sequence: an array of `{ prompt: string, duration?: string ("1"–"15" seconds) }`. When provided it replaces the top-level `prompt` for shot structure.

shot_type string optional

Kling O3 only. Multi-shot structure: `customize` (you define shots) or `intelligent` (the model decides). Default `customize`.

start_image_url string optional

Kling O3 reference-to-video only. Image to use as the first frame of the generated video.

project_id string optional

File this generation's output into one of your PROJECTS (`GET /v1/projects`) the moment the run completes — the same thing the Video Studio does when you have that project open. Access is checked before the run is created, so an unknown id or one you cannot edit is a 404 `project_not_found` / 403 `project_forbidden` at request time rather than a silently unfiled result. Filing itself is best-effort and never fails a run you have already paid for. The run echoes it back as `project_id`.

team_id string optional

Bill this generation to a WORKSPACE (team) credit pool instead of your balance (`GET /v1/teams`). Membership and a spending role are asserted before the run is created — 404 `invalid_team_id` when you are not a member, 403 `team_role_cannot_spend` when your role cannot spend. Requires the `teams:read` scope. The run echoes it back as `team_id`.

marketing_template_id string optional

Optional id of a Marketing Studio ad template (`GET /v1/marketing/templates`). Composes the request server-side from the template RECIPE: the recipe prompt has its `{{product}}` / `{{avatar}}` tokens replaced with the positions your slot media is actually sent in, the slot media is emitted in the recipe's own `slot_order` AHEAD of any brand/moodboard references, and the template's model, aspect ratio, resolution and duration are applied unless you pass your own. Fill the fields the template lists under `recipe.required_inputs`; a missing one → 400 `marketing_slot_missing`. Passing a template for the other media type → 400 `marketing_template_mode_mismatch`. Unknown id → 404 `marketing_template_not_found`. A template whose model has no /v1 alias → 400 `template_model_unavailable` (pass an explicit `model` to run it anyway). A `prompt` you send alongside is ADDITIVE — it says what to CHANGE, so do NOT re-send the recipe prompt.

marketing_format_id string optional

Optional GENRE (`GET /v1/marketing/formats`) — unboxing, tutorial, before-after, star review, comparison and so on. Fragments compose in the order genre → hook → setting, so the genre defines the piece, the hook claims its opening beat, and the setting relocates whatever those produced. WITH `marketing_template_id` the recipe is the base and the three fragments follow it, with your own `prompt` last as a change note. WITHOUT a template your `prompt` IS the base subject description and the fragments are directives applied to it, so they follow your prompt instead. Works WITHOUT a template. Unknown id → 400 `invalid_marketing_format_id`; a genre that does not apply to this media type → 400 `format_mode_mismatch`.

marketing_hook_id string optional

Optional OPENING MECHANIC (`GET /v1/marketing/hooks`) — a physical event in the first two seconds, not a copy angle: the product flying at the creator, an indoor blizzard, the camera being knocked, a held silence. `stunt` or `subtle`. Independent of the format, so any hook can open any genre. Works WITHOUT a template. Unknown id → 400 `invalid_marketing_hook_id`; a hook that does not apply to this media type → 400 `hook_mode_mismatch`.

marketing_setting_id string optional

Optional ENVIRONMENT (`GET /v1/marketing/settings`) — `realistic` (marble kitchen, gym floor, golden hour) or `unrealistic` (airplane wing, volcano rim, shrunk to doll size). Written as a relocation directive so it overrides the scene a template describes. Works without a template. Unknown id → 400 `invalid_marketing_setting_id`.

product_image_url string optional

Fills the template's PRODUCT slot. Takes precedence over `product_id`.

avatar_image_url string optional

Fills the template's AVATAR slot — who appears in the ad. Takes precedence over `avatar_id`. Rejected with 400 `avatar_not_supported_by_template` on a start-frame video template, which animates the product photo itself and has no avatar slot.

product_id string optional

Fills the PRODUCT slot from your stored brand products (`GET /v1/marketing/products`) — no re-upload. Unknown or imageless id → 400 `invalid_product_id`.

avatar_id string optional

Fills the AVATAR slot from your trained influencers or the house avatar library (`GET /v1/marketing/avatars`). Accepts the prefixed id (`influencer:abc`, `preset:xyz`), a bare influencer id, or an @handle. Unknown id → 400 `invalid_avatar_id`.

brand_id string optional

Optional id of one of your brands (`GET /v1/brands`). Grounds the clip server-side: its colors/style/voice lead the prompt, and its product images join `reference_image_urls` only where that cannot change the request's mode — when the clip already runs reference-to-video, or is text-only on a model with a reference endpoint (`capabilities.referenceImages`). With `image_url` (start frame — except on Kling O3 / Kling 4.0, whose reference endpoint keeps the frame), first/last frames or keyframes, or on a model without reference images, the brand grounds by text alone and the clip keeps its mode and inputs. With `source_video_url` the images ride along only where the clip's endpoint takes references (Kling O3 video-to-video reference, H3 Max 3D / Insert, Happy Horse edit) — never on `video.hailuo_03_max_recast`, whose references are the cast, so it grounds by text alone. Invalid or unowned id → 404 `invalid_brand_id`. POST /v1/models/estimate-cost with the same `brand_id` quotes exactly this.

moodboard_id string optional

Optional id of one of your moodboards (`GET /v1/moodboards`, or a board shared with you). Requires the `moodboards:read` scope in addition to `videos:write` (403 `insufficient_scope` otherwise). Styles the clip server-side: the board's composed style fragment follows any brand prefix at the head of the prompt, and its exemplar images join `reference_image_urls` only where that cannot change the request's mode: when the clip already runs reference-to-video (you, the brand or a template sent references), or is text-only on a model with a reference endpoint (`capabilities.referenceImages`) — filling only the reference slots the model has left. With `image_url` (start frame — except on Kling O3 / Kling 4.0, whose reference endpoint keeps the frame), first/last frames or keyframes, or on a model without reference images, the board styles by text alone and the clip keeps its mode and inputs. With `source_video_url` the exemplars ride along only where the clip's endpoint takes references (Kling O3 video-to-video reference, H3 Max 3D / Insert, Happy Horse edit) — never on `video.hailuo_03_max_recast` (its references are the cast; the board styles it by text). Unknown or inaccessible id → 404 `invalid_moodboard_id`. The run's `input` records `moodboard_id` and `moodboard_strength`. POST /v1/models/estimate-cost with the same `moodboard_id` quotes exactly this.

moodboard_strength string optional

How hard the moodboard steers: `subtle` (prompt fragment only), `balanced` (fragment + up to 3 exemplars) or `strong` (fragment + as many exemplars as the model has reference slots for). Any other value → 400 `invalid_moodboard_strength`. Only read with `moodboard_id`.

Default: balanced

loras array optional

Your TRAINED MODELS to run this clip with — `[{ id, scale? }]`, up to 3 ids from `GET /v1/loras` (status `completed`), `scale` 0–4 (default 1). `video.hailuo_03` ONLY: every other model — including `video.hailuo_03_max` and `video.hailuo_03_max_turbo` — returns 400 `invalid_loras`. The model's kind picks the Hailuo 03 endpoint: a `style` or `keyframe` model runs image-to-video (prompt only, or prompt + `image_url`), a `subject` model runs reference-to-video (with or without `reference_image_urls` / video / audio), a `t2v` model runs text-to-video. Mixing kinds, or a start frame with a subject model, is a 400 `lora_mode_mismatch`; an id that is not yours is 404 `lora_not_found`; one still training is 409 `lora_not_ready`. The model's `trigger_phrase` is prepended to your prompt for you. Billed at the Hailuo 03 LoRA per-second rate (POST /v1/models/estimate-cost with `loras` quotes it).

end_image_url string optional

Last frame the clip lands on, paired with `image_url` for image-to-video. Supported by every model whose `capabilities.endFrame` is true at GET /v1/models — the Seedance, Kling V3/O3/2.6, and Hailuo 03/02 families. Sending it to any other model, or in a mode other than image-to-video (Kling O3 reference excepted), returns a 400 rather than silently dropping the input.

quote_token string optional

A signed price quote from the matching estimate endpoint, binding this submit to the price you were shown. May also be sent as the `X-Genfire-Quote` header. The token carries the quoted inputs, so it is only spendable on the request it was issued for: 409 `quote_expired` past its 10-minute life, 409 `quote_mismatch` when the inputs or the price no longer match, 401 `quote_invalid` when the signature does not verify. Every 409 carries a FRESH quote in the problem body so you can resubmit in one step.

curl Example

curl https://api.genfire.ai/v1/videos/generations \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "prompt": "Cinematic close-up of a luxury watch with soft lighting",
  "model": "video.veo_3_1",
  "aspect_ratio": "16:9",
  "duration": 8,
  "generate_audio": true
}'

Response Example

{
  "id": "run_vid_123",
  "object": "run",
  "status": "queued",
  "capability": "video_generation",
  "endpoint": "videos.generations.create",
  "model": "video.veo_3_1",
  "request_id": "run_vid_123",
  "input_summary": {
    "prompt": "Cinematic close-up of a luxury watch with soft lighting",
    "model": "video.veo_3_1",
    "aspect_ratio": "16:9",
    "duration": 8,
    "has_image": false,
    "generate_audio": true
  },
  "output": null,
  "usage": null,
  "error": null,
  "resource_id": "video_123",
  "provider_request_id": null,
  "created_at": "2026-03-25T12:00:00.000Z",
  "updated_at": "2026-03-25T12:00:00.000Z",
  "completed_at": null
}

Request Example Body

{
  "prompt": "Cinematic close-up of a luxury watch with soft lighting",
  "model": "video.veo_3_1",
  "aspect_ratio": "16:9",
  "duration": 8,
  "generate_audio": true
}

Notes

  • Poll `GET /runs/{runId}` until the run moves to `completed` or `failed`.
  • CHAINING SHOTS: the completed run's `output` carries `last_frame_url` — a still of the final frame at the video's resolution, for chaining the next shot; present on completed videos when available. Pass it as `image_url` on the next POST /videos/generations (or as `first_frame_url` / a reference image) to continue from exactly where this clip ends. It is a Genfire storage URL like `video_url`; a video without one simply has no such key.
  • Per-model duration / aspect-ratio / resolution limits are returned by `/models`; passing an unsupported value returns a 400 listing the allowed values.
  • If you provide an input combination the model does not declare in `capabilities`, you get `unsupported_input_combination`.
  • The structured fields `elements`, `multi_prompt`, `shot_type`, and `start_image_url` apply to Kling O3 models only (`video.kling_o3`, `video.kling_o3_pro`, `video.kling_o3_4k`) and are ignored by every other model. Create a Kling `voice_id` for an element with POST `/videos/voices`.
  • `end_image_url` is NOT O3-only: pair it with `image_url` on any model whose `capabilities.endFrame` is true (Seedance 2.0 all tiers, Seedance 2.5, Kling V3/O3/2.6, Hailuo 03, Hailuo 02 Standard) and the clip interpolates from the start image to that last frame. On Kling O3 it rides the reference endpoint alongside `start_image_url`.
  • Kling O3 Standard/Pro also support video-to-video reference: pass `source_video_url` together with `reference_image_urls` (or `elements`) to restyle a source clip while binding reference characters.
  • Hailuo 03 (`video.hailuo_03`, `video.hailuo_03_max`) and the Seedance models additionally accept `reference_video_urls` and `reference_audio_urls`; `video.gemini_omni_flash_v11` accepts `reference_video_urls` but no audio pool. Both Gemini Omni Flash tiers cite references with Google's zero-indexed tags — `<IMAGE_REF_0>` for the first entry of `reference_image_urls`, `<VIDEO_REF_0>` for the first reference clip — and expect each reference's role declared in words ("Use Image1 as a character reference"); a house `@Image1` chip is converted to the tag for you. `video.hailuo_03_max` takes the same three reference pools as `video.hailuo_03` (9 images / 3 videos / 3 audio) at roughly a third of the per-second price, topping out at 1080p (2x the 768p rate) instead of 2K; its reference CLIPS add a token surcharge on their length as measured server-side (the `reference_video_trims` window when one is set, pooled up to 15s; an unreadable clip adds nothing), so quote them with POST /v1/models/estimate-cost. `video.hailuo_03_max_turbo` is the same schema at half the H3 Max price again, but text-to-video and image-to-video ONLY — it has no reference pools at all. Cite every reference positionally in the prompt — `Image 1`/`Video 1`/`Audio 1` on Hailuo 03, `@Image1`/`@Video1`/`@Audio1` on Seedance — or the model has no reason to bind it. An `@handle` mention of one of your influencers automatically attaches both its conditioning images AND its voice clip, with the citation written for you. On `video.hailuo_03` with no `image_url` and no `loras` of your own, a mentioned influencer that has a completed trained video model ALSO attaches that model: the run moves to the reference-to-video LoRA endpoint and bills at the trained-model rate, and `input_summary.lora_auto_attached` is true. `video.hailuo_03` is additionally the ONLY model that runs your trained models (`loras`); H3 Max and H3 Max Turbo have no LoRA endpoints.
  • `video.flux_3_edit` (Flux 3 Edit) takes `source_video_url` + `prompt` ONLY (400 `source_video_url_required` without the clip): the source (MP4, under 15s and 50 MB) is re-rendered at 720p with its motion, timing and framing preserved, so `duration`, `aspect_ratio` and `resolution` are ignored. To CONTINUE a clip instead, send `source_video_url` to `video.flux_3` — that routes to extend.
  • `video.hailuo_03_max_extend` (H3 Max Extend) CONTINUES an existing clip: `source_video_url` (1.625-60s, ≤50 MB) + a prompt describing what happens next are both required. `duration` (5-15) is the length of the NEW footage; `extend_output` picks whether you get the source plus the new footage (`extended`, default) or the new footage alone (`continuation`); `aspect_ratio` defaults to `auto` (follow the source). 480p / 768p / 1080p / 2k on the H3 Max per-second ladder (1080p 2x, 2k 4x the 768p rate), plus a small surcharge for the source clip's tokens (the first 4,096 are free; the charged length is capped at 15s). No image_url, reference pools or text-only mode — each is a 400. Quote it with POST /v1/models/estimate-cost and the same `source_video_url`: the clip is measured and its length is keyed into the quote.
  • `video.hailuo_03_max_insert` (H3 Max Insert) cuts a NEW scene into an existing clip and hands back to the original footage. Required: `source_video_url` (≤60s, ≤50 MB), `insert_start_time` (where the new scene begins, ≥1.625s) and `insert_resume_time` (where the source picks up again — after the start, ≤60s and before the clip ends), both in seconds into `source_video_url` as supplied (it is never trimmed first). Guide the new scene with `prompt` and/or up to 9 `reference_image_urls` and 3 `reference_video_urls` (no reference audio) — at least one of them, or 400 `insert_guidance_required`. The run returns the WHOLE edited clip: the lead-in up to the start, the new scene, then the source from the resume point on. `duration` (5-13s) is the length of the NEW scene only — and the only seconds billed, whatever span it replaces; the source clip itself is not billed. Reference clips add a small token surcharge on their length as measured server-side (the `reference_video_trims` window when one is set; an unreadable clip adds nothing), so quote them with POST /v1/models/estimate-cost; together they may run at most 15s (400 `reference_video_pool_too_long`). `color_match` (default true) grades the new scene to the source. 480p or 768p (default 768p; 480p bills 5/6 of the 768p rate); no `aspect_ratio` (the output follows the source). The source is measured before the run exists: over 60s is 400 `source_video_too_long`, over 50 MB is 400 `source_video_too_large`, and a window that runs off its end is 400 `invalid_insert_window`.
  • `video.hailuo_03_max_recast` (H3 Max Recast) swaps the main people in an existing clip for people from photos, keeping its motion, camera, cuts and sound. Required: `source_video_url` (5-30s, strict; no single shot longer than 15s — that one is not measured, so a longer shot fails at the provider and is refunded) and `reference_image_urls` (1-4 photos, one per new person: photo 1 replaces the leftmost main person, photo 2 the next, and so on, each in every shot they appear in) — 400 `reference_image_urls_required` with none. `prompt` is optional (≤2,000 chars): say who becomes whom ("the woman in photo 2 replaces the man on the right"), or what else to keep. The output has the source's length and framing, so there is no `duration` or `aspect_ratio`; no reference clips or audio. 768p or 1080p (default 1080p; 768p bills 2/3 of the 1080p rate). Billed per second of the SOURCE, rounded up, the photos free: the clip is measured before the run exists — under 5s is 400 `source_video_too_short`, over 30s is 400 `source_video_too_long` (trim it first), and one that cannot be measured is 422 `source_video_unreadable`, never a guessed charge. Estimate with the same `source_video_url` (POST /v1/models/estimate-cost quotes it on the measured length and keys that length into the quote). `brand_id` / `moodboard_id` ground it by text only — their images are never added to the cast.
  • `video.hailuo_03_max_3d` (H3 Max 3D to Video) turns a 3D render — Blender, previs, greybox, game capture — into a photoreal clip that keeps its camera moves, timing, trajectories and object count. `source_video_url` (≤15s, strict) is required; the output matches its length and framing, so `duration` and `aspect_ratio` are ignored. `prompt` is optional scene intent (≤2,000 chars). Up to 8 `reference_image_urls` fix the look of every shot; without them the model plans the shots and generates up to `max_generated_reference_images` (default 2) appearance references itself. 480p / 768p / 1080p (default 768p; no 2k). Billed per request: a flat planning fee (50 credits) + the H3 Max per-second rate on the render's length (whole seconds, 5s minimum) + 10 credits per generated reference + the source clip's tokens. The render is measured before the run exists — always estimate with the same `source_video_url` first.
  • `video.hailuo_03_max_multi_angle` (H3 Max Multi Angle) is image-to-video ONLY — `image_url` is required (400 `image_url_required` without it) and the motion comes from `camera_path` / `camera_trajectory`, not from an end frame or references (it accepts neither). Bills the H3 Max image-to-video rate.
  • `video.hailuo_03_max_styles` (H3 Max Styles) renders the prompt in ONE baked-in look chosen by `video_style` (required): `vhs`, `retro_toon_70s`, `low_poly`, `hand_drawn` or `16bit_pixel`; `damage_level` (`light` / `medium` / `heavy`) tunes the tape wear on `vhs` only. Text-to-video, or image-to-video from an optional `image_url` first frame on the same model — the image's ratio then sets the canvas and `aspect_ratio` is ignored. 5-15s at a fixed 768p with audio always on, one flat per-second rate for every style: there is no `resolution` choice (anything but `768p` is a 400) and a quote does not move with one. A prompt is required (400 `invalid_prompt`) — the style is applied to the scene you describe. No end frame, no reference pools, no trained models: each is a 400, never a silent drop.
  • Trained models: pass `loras: [{ id, scale? }]` with `model: video.hailuo_03` to render through an adapter you trained with POST `/loras` (see the Trained Models section). Up to 3 per clip, all of one kind. The adapter's kind decides the endpoint (style/keyframe → image-to-video, subject → reference-to-video, t2v → text-to-video), so give it the matching inputs — a start frame for a style model, reference media (optional) for a subject model.
  • Seedance 2.5 (`video.seedance_2_5`) is the long-form Seedance AND the Genfire Genjudo engine: 4-30s in a single pass, up to 30 image + 10 video + 10 audio references, 480p/720p/1080p (no 4k). An omitted (or `auto`) `duration` is 5s and an omitted `aspect_ratio` 16:9 — the model never picks a length — EXCEPT with exactly one reference clip (task `reference` or none, ratio omitted, `auto` or the clip's own), where the run matches the clip (see GENFIRE GENJUDO below). One video per request: a `count` above 1 is clamped to 1 (one video, billed once). An `image_url` sent WITH reference media is folded into the reference images — appended after your own `reference_image_urls` as the next `@ImageN` (a 400 only when the 30-image pool is already full); to animate a frame, send it without reference media. `bitrate_mode` has no effect (ignored), and there is no `seed`.
  • DRAFT, THEN FINAL (Seedance 2.5 only). Send `draft: true` to get a cheap 480p preview of the shot at the 480p price. When it looks right, `POST /v1/videos/generations/{id}/final` (Idempotency-Key required) with the draft's run id renders the native 1080p version of that exact shot as a NEW run you poll like any other — no prompt or references to resend. `POST /v1/videos/generations/{id}/final/estimate` prices it first. A draft is completable for 7 days from its creation, and repeatedly.
  • GENFIRE GENJUDO — motion transfer and video edit. Both are one call on `video.seedance_2_5` with a different `task`. MOTION TRANSFER: `task: "reference"`, one clip in `reference_video_urls` and up to 30 stills in `reference_image_urls`, prompt citing them as `@Video1` / `@Image1` ("Transfer the motion in @Video1 onto @Image1 exactly; keep the identity, clothing and proportions of @Image1"). VIDEO EDIT / SWAP: `task: "editing"` with the clip to change in `reference_video_urls` and any replacement still in `reference_image_urls` ("Replace the product shown in @Video1 with the product in @Image1") — the output follows the SOURCE, so `aspect_ratio` and `duration` are coerced to auto and need not be sent. MATCH-THE-CLIP DEFAULTS (exactly one clip): `task` `reference` (or omitted) with no `aspect_ratio` runs at `auto`, and with no (or `auto`) `duration` and the ratio omitted, `auto` or the clip's own runs the clip's own length (the trimmed window when trimmed; whole seconds, rounded UP, 4–30), billed on that length as an edit is, and a render that comes back more than half a second longer is cut to that billed length — `follow_source: true` does the same and replaces a `duration` you send; `reference` or `editing` with neither `generate_audio` nor `keep_source_audio` keeps the clip's own sound (`keep_source_audio: true`) unless `reference_audio_urls` is sent or the clip has no audio track. Send the field to opt out — `aspect_ratio`, `duration`, `generate_audio: true` or `keep_source_audio: false`. `extension` gets none of these defaults (5s unless you send `duration`). With two or more clips the plain defaults apply (16:9, 5s, generated audio). LIMITS: at most 10 reference clips, each 2-30s after any trim, under 200 MB at 24-60 fps, and 30s COMBINED across the pool (`limits.max_reference_video_seconds_total`); duplicate URLs are de-duplicated; `editing` and `extension` take EXACTLY ONE clip, and an edit clip must be at least 4s. Reference audio: up to 10 clips, each 2-30s and at most 15 MB, 30s combined, never the only reference. An overflow is a 400 before anything is charged, never a failed render after. PRICING: a reference run carrying at least one clip bills the Genjudo rate (`seedance_2_5_video_ref`, 20 credits at 720p) PER SECOND OF OUTPUT **plus** per second of reference clip — the provider counts the clip frames as input tokens, so a 15 s edit of a 15 s clip is 30 billable seconds (600 credits at 720p). Images-only reference bills 47/s of output (`seedance_2_5_ref`). x0.47 at 480p, x2.5 at 1080p on both. Recipes with the prompts already written: GET /v1/videos/genjudo/presets; curated reference clips to transfer motion from: GET /v1/videos/genjudo/motion-library.
  • Wan 3.0 (`video.wan_3`) and Wan 3.0 Prime (`video.wan_3_prime`) share one schema: 2-30s, 480p/720p/1080p (default 1080p, no 4k), native synchronized audio, start + end frame, and up to 10 image + 5 video + 5 audio references cited positionally as `Image 1` / `Video 1` / `Audio 1`. They also accept `file_url` (a document) or `web_url` (a public page) as the source the video is built from. Pricing is per second and anchored to 1080p — 720p bills half and 480p a quarter. Prime is the faster, higher-fidelity tier at 1.4x the base price.
POST /videos/generations/{id}/final
videos:write

Render the 1080p final of a Seedance 2.5 draft

Turn a Seedance 2.5 DRAFT (a `POST /videos/generations` run sent with `draft: true`) into the native 1080p video of the SAME shot — prompt, references, duration, aspect ratio and audio are all inherited, so there is nothing to resend. Returns a NEW queued video run; poll `GET /runs/{runId}` (or subscribe to webhooks) exactly like any video generation. Its `resource_id` is the final's own video, and the draft stays as it was. Priced at what a DIRECT 1080p render of the draft's request costs (reference-clip seconds included), reserved when the run starts and refunded if the render fails. Price it first with `POST /videos/generations/{id}/final/estimate`. A draft can be finished for 7 days from its creation, as many times as you like — each Idempotency-Key buys one final.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Parameters

id path required

The DRAFT: its run id (`run_…` from POST /videos/generations, or the `dash_video_…` id GET /runs lists for a draft made in the studio), or its `output.video_id`.

Body Fields

project_id string optional

File this run's output into one of your PROJECTS (`GET /v1/projects`), exactly as if you had the project open in the studio. Edit access is checked before the run is created. Routes whose output is not an image, video or audio a project can hold return 400 `project_filing_unsupported`. The run echoes it back as `project_id`, and `GET /v1/runs?project_id=` lists everything made inside it.

curl Example

curl https://api.genfire.ai/v1/videos/generations/{id}/final \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{}'

Response Example

{
  "id": "run_final_123",
  "object": "run",
  "status": "queued",
  "capability": "video_generation",
  "endpoint": "videos.generations.final",
  "model": "video.seedance_2_5",
  "request_id": "run_final_123",
  "input_summary": {
    "model": "video.seedance_2_5",
    "mode": "draft-final",
    "resolution": "1080p",
    "draft_video_id": "video_draft_123",
    "draft_run_id": "run_draft_123",
    "credits_quoted": 470,
    "draft_expires_at": "2026-10-12T12:00:00.000Z"
  },
  "output": null,
  "usage": null,
  "error": null,
  "resource_id": "video_final_123",
  "provider_request_id": null,
  "created_at": "2026-03-25T12:00:00.000Z",
  "updated_at": "2026-03-25T12:00:00.000Z",
  "completed_at": null
}

Request Example Body

{}

Notes

  • Every refusal is decided BEFORE the run exists, so a corrected retry can reuse its Idempotency-Key: 404 `draft_not_found` (not yours, or no such video), 400 `not_a_draft` (rendered without `draft: true`), 400 `draft_failed` (the draft run failed), 409 `draft_not_ready` (still rendering — poll it first), 410 `draft_expired` (older than 7 days — make a new draft), 400 `draft_not_completable` (a draft from before finals existed), 402 `insufficient_credits`.
  • 503 `draft_unavailable` / `draft_complete_failed` mean the provider could not start the render: the credits are refunded and the run is marked failed — retry with a NEW Idempotency-Key.
  • The final bills whoever paid for the draft — the same workspace (team) pool, with your membership re-checked, or your own balance — so `team_id` is a 400 `team_billing_unsupported` here. `project_id` files the finished video like any generation.
POST /videos/generations/{id}/final/estimate
runs:read

Price the 1080p final of a Seedance 2.5 draft

The exact credits `POST /videos/generations/{id}/final` will reserve for this draft — a direct 1080p render of the draft's own request — and `expires_at`, after which the draft can no longer be finished. Runs the same checks as the render (yours, a draft, finished, inside its 7 days) and the same error codes, without creating anything.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

id path required

The DRAFT: its run id (`run_…`, or a `dash_video_…` id) or its `output.video_id`.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/videos/generations/{id}/final/estimate \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "cost_estimate",
  "model": "video.seedance_2_5",
  "capability": "video_generation",
  "credits": 470,
  "unit": "total",
  "breakdown": {
    "operation_key": "seedance_2_5_ref",
    "resolution": "1080p"
  },
  "draft_video_id": "video_draft_123",
  "draft_run_id": "run_draft_123",
  "expires_at": "2026-10-12T12:00:00.000Z"
}
POST /videos/voices
videos:write

Create a Kling voice

Register a custom Kling voice from a short audio/video sample (5–30s, clean single-voice `.mp3/.wav/.mp4/.mov`). Returns a `voice_id` that can be bound to a Kling O3 element (`elements[].voice_id`) on POST `/videos/generations` so that element speaks in the cloned voice. Synchronous. This is Kling's own voice registry — the id is only meaningful to Kling video models.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Body Fields

voice_url string required

URL of the voice sample. `.mp3/.wav` audio or `.mp4/.mov` video, 5–30 seconds, clean single-voice audio.

team_id string optional

Bill this run to a WORKSPACE (team) credit pool instead of your balance (`GET /v1/teams`). Membership and a spending role are asserted before the run is created — 404 `invalid_team_id` when you are not a member, 403 `team_role_cannot_spend` when your role cannot spend. Requires the `teams:read` scope. A route that cannot bill a pool yet returns 400 `team_billing_unsupported` rather than quietly charging you. The run echoes it back as `team_id`.

curl Example

curl https://api.genfire.ai/v1/videos/voices \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "voice_url": "https://example.com/voice-sample.mp3"
}'

Response Example

{
  "voice_id": "kling_voice_abc123"
}

Request Example Body

{
  "voice_url": "https://example.com/voice-sample.mp3"
}

Notes

  • Bind the returned `voice_id` to an element: `elements: [{ frontal_image_url: "…", voice_id: "kling_voice_abc123" }]` on POST `/videos/generations` with a Kling O3 model.
POST /images/background-remove
images:write

Remove image background

Remove the background from an image using BRIA. Returns a transparent PNG. Synchronous — typically completes in a couple seconds.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Body Fields

image_url string required

Absolute URL of the source image.

project_id string optional

File this generation's output into one of your PROJECTS (`GET /v1/projects`) when the run completes. Access is checked before the run is created; filing itself is best-effort.

team_id string optional

Bill this run to a WORKSPACE (team) credit pool instead of your balance (`GET /v1/teams`). Membership and a spending role are asserted before the run is created — 404 `invalid_team_id` when you are not a member, 403 `team_role_cannot_spend` when your role cannot spend. Requires the `teams:read` scope. A route that cannot bill a pool yet returns 400 `team_billing_unsupported` rather than quietly charging you. The run echoes it back as `team_id`.

quote_token string optional

A signed price quote from the matching estimate endpoint, binding this submit to the price you were shown. May also be sent as the `X-Genfire-Quote` header. The token carries the quoted inputs, so it is only spendable on the request it was issued for: 409 `quote_expired` past its 10-minute life, 409 `quote_mismatch` when the inputs or the price no longer match, 401 `quote_invalid` when the signature does not verify. Every 409 carries a FRESH quote in the problem body so you can resubmit in one step.

curl Example

curl https://api.genfire.ai/v1/images/background-remove \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "image_url": "https://example.com/photo.jpg"
}'

Response Example

{
  "id": "run_bg_123",
  "object": "run",
  "status": "completed",
  "capability": "image_editing",
  "endpoint": "images.background_remove.create",
  "model": "image_edit.bria_background_remove",
  "request_id": "run_bg_123",
  "input_summary": {
    "model": "image_edit.bria_background_remove",
    "image_url": "https://example.com/photo.jpg"
  },
  "output": {
    "images": [
      {
        "url": "https://cdn.genfire.ai/output/bg-removed.png"
      }
    ],
    "output_url": "https://cdn.genfire.ai/output/bg-removed.png"
  },
  "usage": {
    "credits": 1
  },
  "error": null,
  "resource_id": null,
  "provider_request_id": null,
  "created_at": "2026-04-30T12:00:00.000Z",
  "updated_at": "2026-04-30T12:00:01.500Z",
  "completed_at": "2026-04-30T12:00:01.500Z"
}

Request Example Body

{
  "image_url": "https://example.com/photo.jpg"
}
POST /videos/upscale
videos:write

Upscale a video

Upscale a video with Topaz (2x/4x) or Flux (1.5x-3x, FLUX 3 super-resolution). Async — returns a queued run; poll `/runs/{runId}` for completion (typically 1-5 min).

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Body Fields

source_video_url string required

Absolute URL of the source video.

model string optional

`video_upscale.topaz` (Topaz) or `video_upscale.flux_video_upscale` (Flux).

Default: video_upscale.topaz

scale_factor number optional

Topaz: 2 or 4. Flux: any value from 1.5 to 3.

Default: 2

mode string optional

Flux only. `precise` keeps the source faithful; `creative` adds detail.

Default: creative

prompt string optional

Flux only, optional. Guides the creative pass.

team_id string optional

Bill this run to a WORKSPACE (team) credit pool instead of your balance (`GET /v1/teams`). Membership and a spending role are asserted before the run is created — 404 `invalid_team_id` when you are not a member, 403 `team_role_cannot_spend` when your role cannot spend. Requires the `teams:read` scope. A route that cannot bill a pool yet returns 400 `team_billing_unsupported` rather than quietly charging you. The run echoes it back as `team_id`.

quote_token string optional

A signed price quote from the matching estimate endpoint, binding this submit to the price you were shown. May also be sent as the `X-Genfire-Quote` header. The token carries the quoted inputs, so it is only spendable on the request it was issued for: 409 `quote_expired` past its 10-minute life, 409 `quote_mismatch` when the inputs or the price no longer match, 401 `quote_invalid` when the signature does not verify. Every 409 carries a FRESH quote in the problem body so you can resubmit in one step.

curl Example

curl https://api.genfire.ai/v1/videos/upscale \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "source_video_url": "https://cdn.genfire.ai/video.mp4",
  "scale_factor": 2
}'

Response Example

{
  "id": "run_upscale_123",
  "object": "run",
  "status": "queued",
  "capability": "video_upscaling",
  "endpoint": "videos.upscale.create",
  "model": "video_upscale.topaz",
  "request_id": "run_upscale_123",
  "input_summary": {
    "model": "video_upscale.topaz",
    "scale_factor": 2
  },
  "output": null,
  "usage": null,
  "error": null,
  "resource_id": null,
  "provider_request_id": null,
  "created_at": "2026-04-30T12:00:00.000Z",
  "updated_at": "2026-04-30T12:00:00.500Z",
  "completed_at": null
}

Request Example Body

{
  "source_video_url": "https://cdn.genfire.ai/video.mp4",
  "scale_factor": 2
}

Notes

  • Billed per second of video against a tier resolved from the OUTPUT resolution. Flux additionally prices `creative` above `precise`, and costs several times the Topaz rate at every tier.
  • Flux requires an MP4 source of at most 20 seconds and 50MB; over either limit the request is rejected before any credits are reserved.
  • Poll `GET /runs/{runId}` until status is `completed` or `failed`.
POST /images/upscale
images:write

Upscale an image

Upscale an image by 2x or 4x using the Topaz upscaler. Async — returns a queued run; poll `/runs/{runId}` for completion (typically 1-2 min).

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Body Fields

source_image_url string required

Absolute URL of the source image.

scale_factor integer optional

Either 2 or 4.

Default: 2

team_id string optional

Bill this run to a WORKSPACE (team) credit pool instead of your balance (`GET /v1/teams`). Membership and a spending role are asserted before the run is created — 404 `invalid_team_id` when you are not a member, 403 `team_role_cannot_spend` when your role cannot spend. Requires the `teams:read` scope. A route that cannot bill a pool yet returns 400 `team_billing_unsupported` rather than quietly charging you. The run echoes it back as `team_id`.

quote_token string optional

A signed price quote from the matching estimate endpoint, binding this submit to the price you were shown. May also be sent as the `X-Genfire-Quote` header. The token carries the quoted inputs, so it is only spendable on the request it was issued for: 409 `quote_expired` past its 10-minute life, 409 `quote_mismatch` when the inputs or the price no longer match, 401 `quote_invalid` when the signature does not verify. Every 409 carries a FRESH quote in the problem body so you can resubmit in one step.

curl Example

curl https://api.genfire.ai/v1/images/upscale \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "source_image_url": "https://cdn.genfire.ai/image.png",
  "scale_factor": 2
}'

Response Example

{
  "id": "run_img_upscale_123",
  "object": "run",
  "status": "queued",
  "capability": "image_upscaling",
  "endpoint": "images.upscale.create",
  "model": "image_upscale.topaz_upscale_image",
  "request_id": "run_img_upscale_123",
  "input_summary": {
    "model": "image_upscale.topaz_upscale_image",
    "scale_factor": 2
  },
  "output": null,
  "usage": null,
  "error": null,
  "resource_id": null,
  "provider_request_id": null,
  "created_at": "2026-04-30T12:00:00.000Z",
  "updated_at": "2026-04-30T12:00:00.500Z",
  "completed_at": null
}

Request Example Body

{
  "source_image_url": "https://cdn.genfire.ai/image.png",
  "scale_factor": 2
}

Notes

  • Costs scale with the scale factor (4x costs more than 2x).
  • Poll `GET /runs/{runId}` until status is `completed` or `failed`.
POST /videos/compose
videos:write

Cut generated scenes into one finished video

The ASSEMBLY step. Every other generation endpoint makes ONE piece of media; this one cuts pieces you already have into a single finished film — clips in playback order, each optionally carrying its own voiceover, plus audio tracks pinned to absolute timeline positions. This is how a multi-scene ad is produced from primitives: generate each scene with `POST /v1/videos/generations`, generate each line with `POST /v1/audio/speech`, then send them here with one clip per scene and that scene's line as its `audio_url`. It renders through the same ffmpeg engine the faceless-reel, explainer and music-video pipelines use. TIMING, two ways, and they compose: • PER CLIP (`clips[].audio_url`) — the line belongs to that scene and travels with it through every trim and crossfade, so no offset is ever computed by the caller. A line longer than its footage holds the last frame until it finishes (`hold_last_frame: false` cuts instead). • ABSOLUTE (`audio[].start_sec`) — a track pinned to the finished timeline: narration spanning scenes at 0, a looped music bed at `volume: 0.15`, a stinger at 12.4s. Up to 32 tracks, each with its own level. Asynchronous (a long compose is minutes): returns a queued run — poll `GET /v1/runs/{id}`. FREE: no model runs, so no credits are charged; the clips and voiceovers were billed when they were generated.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Body Fields

clips object[] required

Scenes in playback order, 1-60. Each: `url` (absolute https), `kind` ("video" default | "image"), `audio_url` (THIS scene's voiceover), `audio_mode` ("replace" default | "mix" to keep the clip's own sound ducked underneath), `hold_last_frame` (default true), `duration_sec` (image only — defaults to its own audio's length, else 5s), `trim_in_sec`, `trim_out_sec`, `transition_ms` (crossfade INTO this clip; 0 = hard cut), `motion` + `motion_intensity` (image Ken-Burns), `mute_audio`.

audio object[] optional

Up to 32 tracks placed at absolute positions on the finished timeline. Each: `url`, `start_sec` (default 0), `volume` (0-1, default 1), `loop` (repeat until the video ends — for a music bed), `fade_in_sec`, `fade_out_sec` (relative to the track's own start/end, not the timeline).

aspect_ratio string optional

"16:9" (default), "9:16", "1:1", "4:5" or "21:9". Every clip is scaled/cropped to it.

Default: 16:9

fit string optional

"cover" (default) crops clips to fill the frame; "contain" letterboxes them.

Default: cover

transition_ms number optional

Default crossfade for clips that name none. 0 = hard cuts throughout.

Default: 0

clip_audio_volume number optional

Duck EVERY clip's own source audio to this level (0-1) so the tracks sit on top. 0 silences the clips entirely.

captions object optional

{ preset_id, position?, animation?, words_per_line?, text?, words? } — burn captions onto the finished cut. Preset ids come from `GET /v1/faceless-reels/caption-presets`. By default the words are transcribed from the cut's own audio; pass `text` (the authored transcript, ≤20000 chars — force-aligned against that audio so the words stay exactly as written) or `words` (`[{ text, start_sec, end_sec }]`, ≤5000 entries, ascending and non-overlapping — burned verbatim) to skip the guess. `text` and `words` are mutually exclusive. Free, and cosmetic: a caption failure ships the uncaptioned render rather than failing the compose.

project_id string optional

File the finished video into one of your PROJECTS (`GET /v1/projects`) when the compose completes.

title string optional

Title for the finished video in your library.

curl Example

curl https://api.genfire.ai/v1/videos/compose \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "clips": [
    {
      "url": "https://storage.googleapis.com/.../scene1.mp4",
      "audio_url": "https://storage.googleapis.com/.../line1.mp3",
      "transition_ms": 0
    },
    {
      "url": "https://storage.googleapis.com/.../scene2.mp4",
      "audio_url": "https://storage.googleapis.com/.../line2.mp3",
      "transition_ms": 250
    },
    {
      "url": "https://storage.googleapis.com/.../pack.png",
      "kind": "image",
      "duration_sec": 2.5,
      "motion": "kenburns-in"
    }
  ],
  "audio": [
    {
      "url": "https://storage.googleapis.com/.../bed.mp3",
      "start_sec": 0,
      "volume": 0.15,
      "loop": true
    },
    {
      "url": "https://storage.googleapis.com/.../whoosh.mp3",
      "start_sec": 12.4,
      "volume": 0.6
    }
  ],
  "aspect_ratio": "9:16",
  "captions": {
    "preset_id": "hormozi",
    "text": "This serum sold out twice. Here is why."
  },
  "title": "Summer serum ad",
  "quote_id": "quote_9f1c2ab34d5e6f70",
  "expires_at": "2026-09-12T18:24:00.000Z",
  "quote_token": "eyJxIjoicXVvdGVfOWYxYzJhYjM0ZDVlNmY3MCIsLi4ufQ.f3Kb2p9wQm1sZ0yVxT4uR7NcH6L8dJeA5gXqW0oP1sY"
}'

Response Example

{
  "id": "run_9f2c8b1a4d6e4f7a8b9c0d1e2f3a4b5c",
  "object": "run",
  "status": "queued",
  "capability": "video_compose",
  "endpoint": "videos.compose.create",
  "created_at": "2026-08-26T10:30:00.000Z"
}

Request Example Body

{
  "clips": [
    {
      "url": "https://storage.googleapis.com/.../scene1.mp4",
      "audio_url": "https://storage.googleapis.com/.../line1.mp3",
      "transition_ms": 0
    },
    {
      "url": "https://storage.googleapis.com/.../scene2.mp4",
      "audio_url": "https://storage.googleapis.com/.../line2.mp3",
      "transition_ms": 250
    },
    {
      "url": "https://storage.googleapis.com/.../pack.png",
      "kind": "image",
      "duration_sec": 2.5,
      "motion": "kenburns-in"
    }
  ],
  "audio": [
    {
      "url": "https://storage.googleapis.com/.../bed.mp3",
      "start_sec": 0,
      "volume": 0.15,
      "loop": true
    },
    {
      "url": "https://storage.googleapis.com/.../whoosh.mp3",
      "start_sec": 12.4,
      "volume": 0.6
    }
  ],
  "aspect_ratio": "9:16",
  "captions": {
    "preset_id": "hormozi",
    "text": "This serum sold out twice. Here is why."
  },
  "title": "Summer serum ad",
  "quote_id": "quote_9f1c2ab34d5e6f70",
  "expires_at": "2026-09-12T18:24:00.000Z",
  "quote_token": "eyJxIjoicXVvdGVfOWYxYzJhYjM0ZDVlNmY3MCIsLi4ufQ.f3Kb2p9wQm1sZ0yVxT4uR7NcH6L8dJeA5gXqW0oP1sY"
}

Notes

  • Asynchronous: poll `GET /v1/runs/{id}`. The completed run's `output` carries `video_url`, `duration_seconds`, `width`/`height`, and `clips` — where EVERY scene actually landed (`start_sec`/`end_sec`) after trims, holds and crossfade overlaps. Read that back when placing a timed track against a cut you have not seen.
  • FREE. No model runs, so no credits are charged.
  • Clips cap at 60 and tracks at 32. Every url must be an absolute https URL and is checked against the same SSRF guard as any other caller-supplied URL, BEFORE the run exists: 400 `unsafe_outbound_url` when we refuse to reach that target, or 400 `unresolved_outbound_host` when DNS returned nothing for the hostname — a dead or misspelled name rather than a blocked one. Both name the exact field.
  • A crossfade is dropped back to a hard cut wherever it would not fit (longer than either neighbour, or squeezing a clip body under ~3 frames) rather than corrupting the join.
  • Captions: send `captions.text` when you wrote the script (names, brands and numbers survive; transcription mangles them) and `captions.words` when you already have timings — e.g. the `words` a `with_timestamps` speech run returned. Non-monotonic `words` are rejected with 400 `invalid_compose_request`.
POST /videos/timelines
videos:write

Store a re-renderable timeline: exact overlays, retimed clips, keyframed zooms and a finish look

A PERSISTED edit — the half `/videos/compose` cannot express. Compose lays clips end to end. It has no way to say "this logo sits at 62% width, 8% height, at 40% scale, rotated 3°, from 2.0s to 6.5s, on top of the footage", because nothing in its vocabulary puts two things on screen at once. A timeline can: every clip carries a `trackIndex` (Z-order — higher paints on top) and a `layout` of `{ x, y, scale, rotation }` in frame percentages, and the renderer is the same compositor the Genfire video editor exports through. It is also a RESOURCE rather than a request. Store the manifest once, read it back, patch it, render it as many times as you like. Each render is a run keyed on the revision it rendered, so re-rendering an unchanged revision returns the earlier run instead of burning another minute of ffmpeg. SOURCES are declared once in `sources[]` and referenced by every clip that uses them. A source may be a run id (`run_…`, resolved through your own account — build a timeline straight out of what you just generated), an upload `asset_url`, or any absolute https URL. Each is ffprobed at write time, so an unreadable source is a 422 naming the clip that depends on it, not a render that dies a minute in. FREE to store and FREE to render: the media was billed when it was generated.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

clips object[] required

The edit, 1-1000 entries. Each: `id`, `type` ("video" | "image" | "audio" | "text"), `sourceId` (matches a `sources[].id`; omit for text), `startTime` + `duration` (seconds on the finished timeline), `sourceStartTime` (in-point within the source), `trackIndex` (Z-order, 0-64), `layout` ({ x, y: -100..100 percent offsets from centre, scale: 0.2-3, rotation: degrees }), `volume` (0-2), `muted`, and for text clips `textContent` + `textStyle` ({ fontSize, fontWeight, fontFamily, textColor, strokeColor, strokeWidth, backgroundOpacity, captionPresetId, captionWords, captionAnimation, blendMode, gradientColors, gradientDirection }), plus `speed` and `motion` — see below. RETIME: `speed` (video/audio, 0.25-4, default 1) is a playback rate. `duration` stays timeline seconds, so a 2s clip at `speed: 2` consumes 4s of source from `sourceStartTime`, and its audio is time-stretched to match. MOTION: `motion` ({ keyframes, motionBlur? }, video/image; ignored on text) animates the clip's framing on top of `layout` — a crash zoom is two keyframes. Each keyframe is { t, scale, x?, y?, ease? }: `t` is seconds from the CLIP's own start (clamped to its duration), `scale` MULTIPLIES `layout.scale` (0.2-4), `x`/`y` are frame percentages ADDED to `layout.x`/`layout.y`, and `ease` shapes the segment ARRIVING at that keyframe: linear, power1.in, power1.out, power1.inOut, power2.in, power2.out, power2.inOut, back.out, expo.out. At most 16 keyframes. `motionBlur` (0-1) smears the frame while it moves. Out-of-range values are clamped, not rejected. A punch-in needs no motion: split the clip and give the second half a larger `layout.scale`.

sources object[] required

The media the clips point at, one entry per distinct source (max 300). Each: `id` (what `clips[].sourceId` matches) and `ref` — a run id (`run_…`), an upload `asset_url`, or an absolute https URL. Every ref is resolved and ffprobed before the timeline is stored, and the measurement comes back on the stored manifest as `sources[].measured`.

width number required

Frame width in pixels, 16-3840. The frame you authored against; a `final` render is exactly this size. Any aspect works: 1920×1080 (16:9), 1080×1920 (9:16), 1440×1080 (4:3), 1080×1080 (1:1).

height number required

Frame height in pixels, 16-3840.

fps number optional

Frames per second, 1-120.

Default: 30

duration number optional

Timeline length in seconds (max 7200). Defaults to the end of the last clip, and is raised to it if you send something shorter.

graphics object optional

KEYFRAMED OVERLAY LAYERS (`version: 2`). `{ layers: [...], aboveCaptions?: boolean }`. Rendered by a headless browser into ONE transparent track and laid over the footage in a single pass, so it never touches your clips or your audio. Default paint order is footage < graphics < captions; `aboveCaptions: true` puts the graphics on top instead. EVERY LAYER carries `id` (unique, `[A-Za-z0-9][A-Za-z0-9_-]{0,63}`), `start`, `end`, `z` (paint order, ties break on id), `transform` ({ x, y: top-left in AUTHORING-FRAME pixels — the `width`/`height` you declared, not percentages; `width`, `height` optional; `scale`, `rotation` in degrees about the box centre, `opacity` 0-1 }), and optionally `transform3d` ({ rotateX, rotateY, perspective }) for a 2.5D tilt, `mask`, and `keyframes`. KIND is one of four. `text`: `text`, `font`, `size`, `weight` (100-900), `color`, `align` (left|center|right), `lineHeight`, `letterSpacing`, and an optional `entrance` ({ kind: fade-up|pop|typewriter|slide|blur-in, unit: word|char|line, stagger, duration }) that splits the copy and staggers it in. `image`: `src` (an https URL or `source:<id>` naming one of your `sources[]`), `fit` (cover|contain|fill), `radius`. `shape`: `shape` (rect|ellipse|line), `fill`, `stroke`, `strokeWidth`, `radius`. `counter`: `from`, `to`, `ease`, the same type fields as `text`, and `format` ({ style: integer|decimal1|percent, prefix, suffix, group }) — the number is written out on every frame, so scrubbing backwards shows the same digits as scrubbing forwards. KEYFRAMES are `{ property, t, value, ease }[]`. `property` is one of `x`, `y`, `scale`, `rotation`, `opacity`, `rotateX`, `rotateY`. `t` is ABSOLUTE composition time (not an offset from the layer's `start`) and must be strictly increasing within a property and inside `[start, end]`. `ease` describes the segment ARRIVING at that keyframe and is ignored on the first one. A property with no keyframes holds its `transform` value; a property with keyframes starts at its first one. EASES, exactly nine: `linear`, `power1.in`, `power1.out`, `power1.inOut` (quad), `power2.in`, `power2.out`, `power2.inOut` (cubic), `back.out`, `expo.out`. FONTS, exactly two, both vendored into the renderer: `Inter` and `Outfit`, any weight 100-900. Nothing is fetched at render time and there is no fallback family, so the type you ask for is the type you get. MASK is `{ kind: rect|ellipse|image, x, y, width, height, radius, src }`. The box is FRACTIONS of the layer box (0-1, default the whole box) so a mask lands identically at preview and final scale; `rect` takes `radius` as a fraction too, and `image` takes a `src` whose alpha is the matte. COLOURS are hex (`#rgb`, `#rrggbb`, `#rrggbbaa`) or `rgb()`/`rgba()`. Named colours and `var()` are rejected. LIMITS: 50 layers, 200 keyframes per layer, 2000 characters of text per layer.

finish object optional

A LOOK graded over the whole film's footage (video and image clips); text, captions and graphics layers sit on top of it ungraded: { preset, intensity?, grain? }. `preset` is one of `none`, `dvd_35mm`, `camcorder`, `flash_editorial`. `dvd_35mm` is the house look — standard-definition softness, a little compression smear, warm-neutral curves with lifted blacks and rolled highlights, highlight bloom, a soft vignette and moving film grain. `camcorder` is a consumer-video look and `flash_editorial` an on-camera-flash look; `none` is no grade. `intensity` (0-1, default 1) blends the grade; `grain` (0-1, default 0.5) sets the grain alone. An unknown preset is a 400. Omit it and the manifest hashes exactly as before.

title string optional

A name for the timeline.

project_id string optional

Tag this timeline and every render of it with one of your PROJECTS (`GET /v1/projects`), so `GET /v1/runs?project_id=` finds them. The rendered file is NOT filed as a project item — a render produces run output, not a `videos` document a project can hold.

team_id string optional

Team (workspace) this timeline belongs to. Requires the `teams:read` scope.

curl Example

curl https://api.genfire.ai/v1/videos/timelines \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "title": "Serum ad v3",
  "width": 1080,
  "height": 1920,
  "fps": 30,
  "sources": [
    {
      "id": "sc1",
      "ref": "run_9f2c8b1a4d6e4f7a8b9c0d1e2f3a4b5c"
    },
    {
      "id": "logo",
      "ref": "https://storage.googleapis.com/genfire/public-api-uploads/acct_123/logo.png"
    }
  ],
  "clips": [
    {
      "id": "c1",
      "type": "video",
      "sourceId": "sc1",
      "startTime": 0,
      "duration": 6.5,
      "sourceStartTime": 0,
      "trackIndex": 0,
      "speed": 0.8,
      "motion": {
        "keyframes": [
          {
            "t": 5.9,
            "scale": 1
          },
          {
            "t": 6.5,
            "scale": 1.8,
            "ease": "power2.in"
          }
        ],
        "motionBlur": 0.6
      }
    },
    {
      "id": "c2",
      "type": "image",
      "sourceId": "logo",
      "startTime": 2,
      "duration": 4.5,
      "trackIndex": 1,
      "layout": {
        "x": 12,
        "y": -42,
        "scale": 0.4,
        "rotation": 3
      }
    },
    {
      "id": "c3",
      "type": "text",
      "startTime": 1,
      "duration": 3,
      "trackIndex": 2,
      "textContent": "Sold out twice.",
      "textStyle": {
        "fontSize": 72,
        "fontWeight": 800,
        "textColor": "#ffffff",
        "backgroundOpacity": 0
      }
    }
  ],
  "finish": {
    "preset": "dvd_35mm",
    "intensity": 0.8,
    "grain": 0.5
  }
}'

Response Example

{
  "id": "tl_4b1c8d2e9f6a4c7b8d0e1f2a3b4c5d6e",
  "object": "timeline",
  "rev": 1,
  "manifest_hash": "9c1f…",
  "title": "Serum ad v3",
  "project_id": null,
  "manifest": {
    "version": 1,
    "duration": 6.5,
    "width": 1080,
    "height": 1920,
    "fps": 30,
    "clips": [
      "…"
    ],
    "sources": [
      "…"
    ]
  },
  "created_at": "2026-09-12T10:30:00.000Z",
  "updated_at": "2026-09-12T10:30:00.000Z"
}

Request Example Body

{
  "title": "Serum ad v3",
  "width": 1080,
  "height": 1920,
  "fps": 30,
  "sources": [
    {
      "id": "sc1",
      "ref": "run_9f2c8b1a4d6e4f7a8b9c0d1e2f3a4b5c"
    },
    {
      "id": "logo",
      "ref": "https://storage.googleapis.com/genfire/public-api-uploads/acct_123/logo.png"
    }
  ],
  "clips": [
    {
      "id": "c1",
      "type": "video",
      "sourceId": "sc1",
      "startTime": 0,
      "duration": 6.5,
      "sourceStartTime": 0,
      "trackIndex": 0,
      "speed": 0.8,
      "motion": {
        "keyframes": [
          {
            "t": 5.9,
            "scale": 1
          },
          {
            "t": 6.5,
            "scale": 1.8,
            "ease": "power2.in"
          }
        ],
        "motionBlur": 0.6
      }
    },
    {
      "id": "c2",
      "type": "image",
      "sourceId": "logo",
      "startTime": 2,
      "duration": 4.5,
      "trackIndex": 1,
      "layout": {
        "x": 12,
        "y": -42,
        "scale": 0.4,
        "rotation": 3
      }
    },
    {
      "id": "c3",
      "type": "text",
      "startTime": 1,
      "duration": 3,
      "trackIndex": 2,
      "textContent": "Sold out twice.",
      "textStyle": {
        "fontSize": 72,
        "fontWeight": 800,
        "textColor": "#ffffff",
        "backgroundOpacity": 0
      }
    }
  ],
  "finish": {
    "preset": "dvd_35mm",
    "intensity": 0.8,
    "grain": 0.5
  }
}

Notes

  • Storing a timeline renders nothing. Call `POST /v1/videos/timelines/{id}/renders` to produce a file.
  • `rev` starts at 1 and increments on every PATCH. You need it to patch, and renders are keyed on it.
  • Every source is ffprobed before the timeline is stored: an unreadable one is 422 `media_unreadable` naming the first clip that depends on it, and a clip that outruns its source is 400.
  • Every source URL passes the same SSRF guard as any other caller-supplied URL, before anything is probed or stored: 400 `unsafe_outbound_url` when we refuse to reach that target, or 400 `unresolved_outbound_host` when DNS returned nothing for the hostname. Both name the clip index and the source id.
  • Limits: 1000 clips, 300 distinct sources, 7200s total, 3600s per clip. Graphics add: 50 layers, 200 keyframes per layer.
  • Send `graphics` and the stored manifest comes back at `version: 2`; omit it and it stays at `version: 1` and hashes exactly as it always did. Nothing else about the request or the render changes.
  • A bad layer is 400 `invalid_graphics` naming the exact path — `graphics.layers[3].ease must be one of: ...`. It is raised BEFORE any source is resolved or probed, so a typo costs one round trip rather than a full probe pass.
  • Whether the layers FIT is answered at render time, not here: a layer that overflows the frame fails the render run with `graphics_layout_invalid` carrying the overflow the layout audit measured.
  • `speed`, `motion` and `finish` are additive: a manifest that uses none of them stores, hashes and renders exactly as before, and none of them changes the manifest `version`.
  • Transitions are still NOT in the manifest. They extend it additively, so a stored timeline stays readable when they land.
GET /videos/timelines/{timelineId}
videos:write

Read a stored timeline

The timeline as stored: its current `rev`, its `manifest_hash`, and the full manifest including each source's measured duration and dimensions. Read this before a PATCH — the `rev` it returns is what the PATCH must send back.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

timelineId path required

The timeline id returned by `POST /v1/videos/timelines`.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/videos/timelines/{timelineId} \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "id": "tl_4b1c8d2e9f6a4c7b8d0e1f2a3b4c5d6e",
  "object": "timeline",
  "rev": 3,
  "manifest_hash": "9c1f…",
  "title": "Serum ad v3",
  "project_id": null,
  "manifest": {
    "version": 1,
    "duration": 6.5,
    "width": 1080,
    "height": 1920,
    "fps": 30,
    "clips": [
      "…"
    ],
    "sources": [
      "…"
    ]
  },
  "created_at": "2026-09-12T10:30:00.000Z",
  "updated_at": "2026-09-12T11:02:11.000Z"
}

Notes

  • A timeline belonging to another account answers 404, never 403.
PATCH /videos/timelines/{timelineId}
videos:write

Replace or edit a timeline's manifest

Two shapes, one revision guard. Either a full manifest REPLACE — same body as `POST /v1/videos/timelines` — or a list of `operations` that edit clips, sources and layers BY ID, change the frame, or set the finish, preserving everything they do not name. Both bump `rev` and recompute `manifest_hash`, and both validate the whole result before saving. A replace is the right tool once clips are reordered or removed wholesale; `operations` is the right tool for "retime c4", "crash-zoom the last clip" or "make it 4:3". The revision you are editing is REQUIRED, as an `If-Match` header or a `rev` body field. Two agents editing one timeline is the normal case here, and an unconditional replace is exactly the operation that silently discards the other one's edit.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

timelineId path required

The timeline to replace.

Body Fields

rev number required

The revision you read. Sent here or as an `If-Match` header (the header wins). A mismatch is 409 `timeline_rev_conflict`; neither is 428.

operations object[] optional

TARGETED EDITS instead of a replacement manifest (send one or the other, never both). 1-100 of: `{ op: add|update|remove, target: clip|source|layer, id, value? }` — `update` merges `value` into that entity recursively (arrays replace), so `{ op: "update", target: "clip", id: "c1", value: { speed: 2 } }` retimes one clip and leaves the rest alone; `{ op: "frame", value: { width?, height?, fps?, duration? } }` — e.g. `{ width: 1440, height: 1080 }` for 4:3; `{ op: "finish", value: { preset?, intensity?, grain? } | null }` — merged onto the current finish (a preset is required if there is none yet), `null` removes it. The whole result is validated exactly like a replace before it is saved.

clips object[] optional

The complete new clip list — same shape as on create. Required on a replace.

sources object[] optional

The complete new source table — same shape as on create. Re-resolved and re-probed on every PATCH. Required on a replace.

width number optional

Frame width in pixels, 16-3840. Required on a replace.

height number optional

Frame height in pixels, 16-3840. Required on a replace.

fps number optional

Frames per second, 1-120.

Default: 30

duration number optional

Timeline length in seconds.

graphics object optional

The complete new graphics block — same shape as on create. Omitting it REMOVES the layers and drops the manifest back to `version: 1`, like every other field on a replace.

finish object optional

The whole-film look — same shape as on create. Omitting it on a replace REMOVES the look.

title string optional

Rename the timeline. Omit to leave the title alone.

curl Example

curl https://api.genfire.ai/v1/videos/timelines/{timelineId} \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "rev": 3,
  "width": 1080,
  "height": 1920,
  "fps": 30,
  "sources": [
    {
      "id": "sc1",
      "ref": "run_9f2c8b1a4d6e4f7a8b9c0d1e2f3a4b5c"
    }
  ],
  "clips": [
    {
      "id": "c1",
      "type": "video",
      "sourceId": "sc1",
      "startTime": 0,
      "duration": 8,
      "trackIndex": 0
    }
  ]
}'

Response Example

{
  "id": "tl_4b1c8d2e9f6a4c7b8d0e1f2a3b4c5d6e",
  "object": "timeline",
  "rev": 4,
  "manifest_hash": "e07b…",
  "title": "Serum ad v3",
  "project_id": null,
  "manifest": {
    "version": 1,
    "duration": 8,
    "width": 1080,
    "height": 1920,
    "fps": 30,
    "clips": [
      "…"
    ],
    "sources": [
      "…"
    ]
  },
  "created_at": "2026-09-12T10:30:00.000Z",
  "updated_at": "2026-09-12T11:20:44.000Z"
}

Request Example Body

{
  "rev": 3,
  "width": 1080,
  "height": 1920,
  "fps": 30,
  "sources": [
    {
      "id": "sc1",
      "ref": "run_9f2c8b1a4d6e4f7a8b9c0d1e2f3a4b5c"
    }
  ],
  "clips": [
    {
      "id": "c1",
      "type": "video",
      "sourceId": "sc1",
      "startTime": 0,
      "duration": 8,
      "trackIndex": 0
    }
  ]
}

Notes

  • On a replace, send the whole manifest. Fields you leave out are gone, not preserved. Use `operations` to change one clip, the frame or the finish without restating the rest.
  • A successful PATCH invalidates nothing: renders of earlier revisions keep their runs and their files.
POST /videos/timelines/{timelineId}/renders
videos:write

Render a timeline revision to a video

Render the timeline's CURRENT revision through the same compositor the Genfire video editor exports through — exact overlays, z-ordered by track, with the audio of every unmuted clip mixed at its own level. TWO MODES, one renderer. `preview` scales the frame to 480px on its short edge; `final` renders at the manifest's own `width`/`height`. Nothing else differs, so a preview is an honest proxy for the final rather than a second renderer that can disagree with it. Preview bytes are cached by manifest hash, so looking at the same revision twice costs one ffmpeg pass. Asynchronous: returns a run — poll `GET /v1/runs/{id}`. FREE.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Parameters

timelineId path required

The timeline to render.

Body Fields

mode string optional

"preview" (480p on the short edge, cached) or "final" (the manifest's full frame).

Default: preview

rev number optional

Render only if the timeline is still at this revision. A guard, not a selector — there is no way to render a superseded revision, so a stale `rev` is 409 rather than a render of something you did not ask for.

curl Example

curl https://api.genfire.ai/v1/videos/timelines/{timelineId}/renders \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "mode": "final"
}'

Response Example

{
  "id": "run_7a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d",
  "object": "run",
  "status": "queued",
  "capability": "timeline_render",
  "endpoint": "videos.timelines.renders.create",
  "created_at": "2026-09-12T11:22:00.000Z"
}

Request Example Body

{
  "mode": "final"
}

Notes

  • The completed run's `output` carries `video_url`, `duration_seconds`, `width`, `height`, `mode`, `rev`, and `measured` — what ffprobe read off the file that was actually produced, not what was requested.
  • A `version: 2` manifest also renders its graphics, at the SAME frame as the picture, and reports `output.graphics: { layers, frames, render_ms }` so a slow render can be attributed to the overlay or to the footage.
  • Two typed graphics failures, both terminal — neither improves on a retry. `graphics_layout_invalid`: the composition did not pass its layout audit (text overflowing its box is the usual cause) and the message carries the findings. `graphics_render_failed`: the renderer or the alpha encode itself failed. A missing image is `graphics_asset_unavailable`.
  • The run is fingerprinted on `{ timeline_id, rev, manifest_hash, mode }`: re-rendering an unchanged revision returns the earlier run, and a PATCHed manifest is a new one. A FAILED render retries on the same Idempotency-Key.
  • FREE. No model runs, so no credits are charged.
  • Renders run on the same worker as editor exports. A long final render is minutes; a cached preview returns almost immediately with `output.cached: true`.
GET /videos/timelines/{timelineId}/renders
videos:write

List a timeline's renders

Every render run for this timeline, newest first — across all revisions and both modes. The way to answer "have I already rendered this revision at full size, and where did it land".

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

timelineId path required

The timeline whose renders to list.

limit query optional

How many runs to return, 1-100.

Default: 25

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/videos/timelines/{timelineId}/renders \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "run_7a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d",
      "object": "run",
      "status": "completed",
      "capability": "timeline_render",
      "endpoint": "videos.timelines.renders.create",
      "resource_id": "tl_4b1c8d2e9f6a4c7b8d0e1f2a3b4c5d6e",
      "output": {
        "video_url": "https://…/render.mp4",
        "duration_seconds": 8,
        "width": 1080,
        "height": 1920,
        "mode": "final",
        "rev": 4
      },
      "created_at": "2026-09-12T11:22:00.000Z"
    }
  ]
}

Notes

  • Each run's `resource_id` is the timeline id and its `output.rev` is the revision it rendered.
POST /lipsync/generations
lipsync:write

Generate a lip-synced video

Lip-sync an existing video — or, with `lipsync.h3_max_lipsync`, animate a photo — to either a hosted audio file or a base64-encoded audio payload.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Body Fields

video_url string optional

Absolute URL to the source video. Required for every model except the photo-input `lipsync.h3_max_lipsync`.

image_url string optional

Absolute URL to the photo to animate. Required for (and only accepted by) `lipsync.h3_max_lipsync`. Aspect ratio must be between 0.4 and 2.5.

resolution string optional

`lipsync.h3_max_lipsync` only: `480P`, `768P`, `1080P` (2x) or `2K` (4x).

Default: 768P

enable_transcription boolean optional

`lipsync.h3_max_lipsync` only: transcribe the audio to guide the mouth shapes.

Default: false

audio_url string optional

Absolute URL to the source audio.

audio_base64 string optional

Base64-encoded audio content. Provide this instead of `audio_url` when uploading audio inline.

audio_file_name string optional

Optional file name to associate with inline audio.

title string optional

Optional title used for the internal video record.

Default: AI Lip-sync Video

description string optional

Optional description for the internal video record.

sync_mode string optional

How the video should align to the audio.

Default: cut_off

model string optional

Optional lipsync model alias from `/models`.

Default: lipsync.lipsync_2_pro

duration number optional

Optional duration hint between 1 and 300 seconds.

project_id string optional

File this generation's output into one of your PROJECTS (`GET /v1/projects`) when the run completes. Access is checked before the run is created; filing itself is best-effort.

team_id string optional

Bill this run to a WORKSPACE (team) credit pool instead of your balance (`GET /v1/teams`). Membership and a spending role are asserted before the run is created — 404 `invalid_team_id` when you are not a member, 403 `team_role_cannot_spend` when your role cannot spend. Requires the `teams:read` scope. A route that cannot bill a pool yet returns 400 `team_billing_unsupported` rather than quietly charging you. The run echoes it back as `team_id`.

quote_token string optional

A signed price quote from the matching estimate endpoint, binding this submit to the price you were shown. May also be sent as the `X-Genfire-Quote` header. The token carries the quoted inputs, so it is only spendable on the request it was issued for: 409 `quote_expired` past its 10-minute life, 409 `quote_mismatch` when the inputs or the price no longer match, 401 `quote_invalid` when the signature does not verify. Every 409 carries a FRESH quote in the problem body so you can resubmit in one step.

curl Example

curl https://api.genfire.ai/v1/lipsync/generations \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "video_url": "https://example.com/source-video.mp4",
  "audio_url": "https://example.com/source-audio.mp3",
  "sync_mode": "cut_off",
  "model": "lipsync.lipsync_2_pro"
}'

Response Example

{
  "id": "run_vid_123",
  "object": "run",
  "status": "queued",
  "capability": "lipsync_generation",
  "endpoint": "lipsync.generations.create",
  "model": "lipsync.lipsync_2_pro",
  "request_id": "run_vid_123",
  "input_summary": {
    "prompt": "Cinematic close-up of a luxury watch with soft lighting",
    "model": "video.veo_3_1",
    "aspect_ratio": "16:9",
    "duration": 8,
    "has_image": false,
    "generate_audio": true
  },
  "output": null,
  "usage": null,
  "error": null,
  "resource_id": "video_123",
  "provider_request_id": null,
  "created_at": "2026-03-25T12:00:00.000Z",
  "updated_at": "2026-03-25T12:00:00.000Z",
  "completed_at": null
}

Request Example Body

{
  "video_url": "https://example.com/source-video.mp4",
  "audio_url": "https://example.com/source-audio.mp3",
  "sync_mode": "cut_off",
  "model": "lipsync.lipsync_2_pro"
}

Notes

  • Provide either `audio_url` or `audio_base64`.
  • Models take ONE source shape: Sync models take `video_url`; `lipsync.h3_max_lipsync` takes `image_url`. `/models` marks the photo-input model with `capabilities.imageToOutput`.
  • `lipsync.h3_max_lipsync` needs 5–14.8 s of audio. Shorter audio is a 400 (`audio_too_short`); longer audio is clipped at 14.8 s and billed for 14.8 s. `sync_mode` does not apply to it.
  • This endpoint is async and usually returns a queued run first.
POST /audio/speech
audio:write

Generate speech audio

Text-to-speech generation using a public speech model alias.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Body Fields

text string optional

The text to speak. Required unless `dialogue` is sent. For `speech.seed_audio_1_0` this is a free-form audio prompt (max 2048 chars) that can also describe scenes/dialogue and reference audio inputs as @Audio1–@Audio3.

voice_id string optional

The voice id to use: either a stock ElevenLabs voice id or one of your cloned voice ids (`clone_<id>`). List options via `/audio/voices`. Required for ElevenLabs models. For `speech.seed_audio_1_0` it is OPTIONAL and takes a Seed preset name (e.g. `vivi_mixed_en_zh_ja_es_id`) instead.

model string optional

Optional speech model alias from `/models`. The default, `speech.elevenlabs_v4_turbo`, is Eleven v4 expressiveness (audio tags) at ~100 ms latency and half v4's cost; `speech.elevenlabs_v4` is the most expressive. Both cap a request at 10,000 characters and take only `stability` + `similarity_boost` voice settings (`speed`, `style` and `use_speaker_boost` are ignored). With `model` omitted, the request runs on `speech.elevenlabs_flash_v2_5` (40,000 characters, same price) instead when the text is longer than 10,000 characters, when it sets a `speed` other than 1, or when `voice_id` is one of your account's cloned ElevenLabs voices (ElevenLabs asks for those to be retrained for v4). `speech.elevenlabs_turbo_v2_5` still works but is deprecated upstream.

Default: speech.elevenlabs_v4_turbo

dialogue object[] optional

Text to Dialogue (`speech.elevenlabs_dialogue_v4` or `speech.elevenlabs_dialogue_v3`, implied when present): an ORDERED array of `{ text, voice_id }` lines rendered as one audio file with matched prosody. Runs on Eleven v3 when `model` is `speech.elevenlabs_dialogue_v3` or `speech.elevenlabs_v3`, otherwise on Eleven v4 (the default dialogue model); the run records and bills that dialogue model. Up to 10 distinct ElevenLabs voices and 2000 characters in total; lines accept audio tags like `[whispers]` or `[laughs]`. Replaces `text` + `voice_id`. Cloned `clone_*` voices are not supported here.

voice_name string optional

Optional human-readable voice name passed through to the audio generator.

language_code string optional

ElevenLabs only — ISO 639-1 code (e.g. `es`, `pt`) that enforces the spoken language on Flash/Turbo/v3/v4. Ignored by Multilingual v2 (it auto-detects), by cloned voices, and by any model that does not support the given language.

speed number optional

Speaking rate. ElevenLabs: 0.7–1.2 (1 = natural, clamped). Sending `speed` without a model uses `speech.elevenlabs_flash_v2_5` automatically; an explicit v4 model (`speech.elevenlabs_v4`, `speech.elevenlabs_v4_turbo`) ignores it. Seed Audio 1.0: 0.5–2.

seed integer optional

ElevenLabs only — 0..4294967295 for best-effort reproducibility (also applies to `dialogue`).

previous_text string optional

ElevenLabs only — the text spoken immediately BEFORE this request (request stitching). Keeps prosody continuous when a long script is generated in chunks.

next_text string optional

ElevenLabs only — the text spoken immediately AFTER this request (request stitching).

apply_text_normalization string optional

ElevenLabs only — `auto` (default), `on` (always expand numbers/abbreviations; adds latency) or `off` (read exactly as written).

Default: auto

voice_settings object optional

ElevenLabs only — `{ stability, similarity_boost, style, use_speaker_boost, speed }` overrides; the v4 models use only `stability` + `similarity_boost`. For `dialogue`, `stability` is a top-level field: snapped to 0 | 0.5 | 1 on v3, any value 0–1 on v4.

with_timestamps boolean optional

ElevenLabs only — return per-word `words: [{ word, start, end }]` in the run output (and `voice_segments` for `dialogue`). Derived from the exact input text, so nothing is misheard.

Default: false

output_format string optional

Optional output format when supported by the backing provider. For `speech.seed_audio_1_0`: one of `wav`, `mp3`, `pcm`, `ogg_opus` (default `mp3`).

audio_urls string[] optional

Seed Audio 1.0 only — up to 3 reference audio URLs (each ≤30s / 10MB), referenced in the prompt as @Audio1–@Audio3. Cannot be combined with `image_url`.

image_url string optional

Seed Audio 1.0 only — a single reference image URL (jpeg/png/webp ≤10MB). Cannot be combined with `audio_urls`.

sample_rate number optional

Seed Audio 1.0 only — output sample rate in Hz: 8000, 16000, 24000 (default), 32000, 44100 or 48000.

speed number optional

Seed Audio 1.0 only — speech speed 0.5–2 (1 = normal).

volume number optional

Seed Audio 1.0 only — volume 0.5–2 (1 = normal).

pitch number optional

Seed Audio 1.0 only — pitch shift in semitones, -12 to 12 (0 = normal).

team_id string optional

Bill this run to a WORKSPACE (team) credit pool instead of your balance (`GET /v1/teams`). Membership and a spending role are asserted before the run is created — 404 `invalid_team_id` when you are not a member, 403 `team_role_cannot_spend` when your role cannot spend. Requires the `teams:read` scope. A route that cannot bill a pool yet returns 400 `team_billing_unsupported` rather than quietly charging you. The run echoes it back as `team_id`.

project_id string optional

File this run's output into one of your PROJECTS (`GET /v1/projects`), exactly as if you had the project open in the studio. Edit access is checked before the run is created. Routes whose output is not an image, video or audio a project can hold return 400 `project_filing_unsupported`. The run echoes it back as `project_id`, and `GET /v1/runs?project_id=` lists everything made inside it.

quote_token string optional

A signed price quote from the matching estimate endpoint, binding this submit to the price you were shown. May also be sent as the `X-Genfire-Quote` header. The token carries the quoted inputs, so it is only spendable on the request it was issued for: 409 `quote_expired` past its 10-minute life, 409 `quote_mismatch` when the inputs or the price no longer match, 401 `quote_invalid` when the signature does not verify. Every 409 carries a FRESH quote in the problem body so you can resubmit in one step.

curl Example

curl https://api.genfire.ai/v1/audio/speech \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "text": "Introducing the new Genfire creative workflow.",
  "voice_id": "voice_abc123",
  "model": "speech.elevenlabs_v4_turbo",
  "language_code": "en"
}'

Response Example

{
  "id": "run_speech_123",
  "object": "run",
  "status": "completed",
  "capability": "speech_generation",
  "endpoint": "audio.speech.create",
  "model": "speech.elevenlabs_v4_turbo",
  "request_id": "run_img_123",
  "input_summary": {
    "prompt": "Studio product photo of a matte black water bottle",
    "model": "image.nano_banana_2",
    "aspect_ratio": "1:1",
    "count": 1
  },
  "output": {
    "audio_id": "audio_123",
    "audio_url": "https://cdn.genfire.ai/output/audio-123.mp3",
    "duration": 6.4
  },
  "usage": {
    "credits": 8
  },
  "error": null,
  "resource_id": null,
  "provider_request_id": null,
  "team_id": null,
  "project_id": null,
  "created_at": "2026-03-25T12:00:00.000Z",
  "updated_at": "2026-03-25T12:00:02.000Z",
  "completed_at": "2026-03-25T12:00:02.000Z"
}

Request Example Body

{
  "text": "Introducing the new Genfire creative workflow.",
  "voice_id": "voice_abc123",
  "model": "speech.elevenlabs_v4_turbo",
  "language_code": "en"
}
POST /audio/music
audio:write

Generate music

Create music from a prompt or a structured composition plan. You can request a simple audio result or a more detailed music result with timestamps.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Body Fields

prompt string optional

Description of the music you want generated. Required unless `composition_plan` is provided; the two cannot be combined.

composition_plan object optional

Structured composition plan instead of a prompt (ElevenLabs only). music_v1 shape: `{ positive_global_styles[], negative_global_styles[], sections[] }` with per-section styles, duration_ms (3000-120000) and lyric lines. music_v2 / music_v2_5 shape: `{ chunks[] }` where each chunk has `text` (may carry [Section] and {direction} markup), `duration_ms`, `positive_styles[]` and optional audio-conditioning refs. Duration and billing derive from the plan's summed durations.

model string optional

Optional music model alias from `/models`. Omitted, it is `music.elevenlabs_music_v2_5` — the newest ElevenLabs model — and a chunks-shaped `composition_plan` also runs on it. `music.elevenlabs_music_v1` is deprecated upstream; a sections-shaped `composition_plan` still selects it. `music.elevenlabs_music_v2` stays available when named and takes the same prompt, chunks plan, timestamps and inpainting fields as v2.5.

Default: music.elevenlabs_music_v2_5

video_url string optional

Video-to-music (ElevenLabs only): score this footage instead of composing from a prompt. The track's length equals the video's (≤600s, ≤200MB); `prompt` becomes an optional description (≤1000 chars) and `tags` steer the style. Cannot be combined with `composition_plan`; billed per second of video.

tags string[] optional

Video-to-music only — up to 10 style tags (e.g. `cinematic`, `upbeat`).

store_for_inpainting boolean optional

ElevenLabs only — keep the song server-side so it can be inpainted. With `include_details: true` the run output carries `song_id`; reference it in a later chunks-shaped (music_v2 / music_v2_5) `composition_plan` as `{ song_id, range: { start_ms, end_ms } }` chunks to keep those bars verbatim and regenerate only the other chunks.

Default: false

duration_seconds number optional

Desired music length in seconds. ElevenLabs prompt mode: 3-600 (default 30; ignored with `composition_plan`). MiniMax Music 3: an upper bound of 1-300 (default 60) that billing is charged on — the model may finish earlier. Both Lyria models ignore it — `music.lyria3_pro` produces a ~3 min song, `music.lyria_3_5` a full-length one whose length you steer in the prompt (timestamps or a duration hint).

Default: 30

lyrics string optional

The lyrics to sing. REQUIRED for `music.minimax_music_3` — that model writes none of its own. Structure tags ([intro], [verse], [pre-chorus], [chorus], [post-chorus], [bridge], [instrumental], [solo], [outro]) must each be on their own line; Genfire splits a tag off a shared line for you so no lyric is silently dropped. Ignored by ElevenLabs (use `composition_plan`) and Lyria 3 Pro (it writes its own).

num_inference_steps integer optional

MiniMax Music 3 only: flow-matching Euler steps per 8-second chunk, 1-100. Higher is better quality and slower.

Default: 30

guidance_scale number optional

MiniMax Music 3 only: classifier-free guidance scale of the flow-matching stage, 0-20. Higher follows the prompt more literally.

Default: 1.7

include_details boolean optional

Return detailed music metadata when supported (ElevenLabs only).

Default: false

with_timestamps boolean optional

Include timestamps when `include_details` is true (ElevenLabs only).

Default: false

force_instrumental boolean optional

Guarantee instrumental output (ElevenLabs prompt mode only).

respect_sections_durations boolean optional

With a music_v1 `composition_plan`: enforce section durations exactly instead of letting the model flex them (music_v2 and music_v2_5 always enforce them).

Default: true

seed integer optional

Random seed for reproducible results (ElevenLabs `composition_plan` mode, or MiniMax Music 3). The seed actually used is returned in the run output.

image_url string optional

Optional image URL used as inspiration for the generated music. `music.lyria_3_5` and `music.lyria3_pro` only — the model matches the mood and theme of the image.

negative_prompt string optional

Deprecated — no current music model supports negative prompting (Lyria 3 rejects it, MiniMax Music 3 has no such field); the field is ignored.

output_format string optional

Optional output format when supported by the backing provider (ElevenLabs default `auto` picks the best mp3 for the model).

team_id string optional

Bill this run to a WORKSPACE (team) credit pool instead of your balance (`GET /v1/teams`). Membership and a spending role are asserted before the run is created — 404 `invalid_team_id` when you are not a member, 403 `team_role_cannot_spend` when your role cannot spend. Requires the `teams:read` scope. A route that cannot bill a pool yet returns 400 `team_billing_unsupported` rather than quietly charging you. The run echoes it back as `team_id`.

project_id string optional

File this run's output into one of your PROJECTS (`GET /v1/projects`), exactly as if you had the project open in the studio. Edit access is checked before the run is created. Routes whose output is not an image, video or audio a project can hold return 400 `project_filing_unsupported`. The run echoes it back as `project_id`, and `GET /v1/runs?project_id=` lists everything made inside it.

quote_token string optional

A signed price quote from the matching estimate endpoint, binding this submit to the price you were shown. May also be sent as the `X-Genfire-Quote` header. The token carries the quoted inputs, so it is only spendable on the request it was issued for: 409 `quote_expired` past its 10-minute life, 409 `quote_mismatch` when the inputs or the price no longer match, 401 `quote_invalid` when the signature does not verify. Every 409 carries a FRESH quote in the problem body so you can resubmit in one step.

curl Example

curl https://api.genfire.ai/v1/audio/music \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "prompt": "Confident electronic music bed for a product launch video",
  "model": "music.elevenlabs_music_v2_5",
  "duration_seconds": 30,
  "include_details": false
}'

Response Example

{
  "id": "run_music_123",
  "object": "run",
  "status": "completed",
  "capability": "music_generation",
  "endpoint": "audio.music.create",
  "model": "music.elevenlabs_music_v2_5",
  "request_id": "run_img_123",
  "input_summary": {
    "prompt": "Studio product photo of a matte black water bottle",
    "model": "image.nano_banana_2",
    "aspect_ratio": "1:1",
    "count": 1
  },
  "output": {
    "audio_id": "audio_music_123",
    "audio_url": "https://cdn.genfire.ai/output/music-123.mp3",
    "duration": 30,
    "song_id": null
  },
  "usage": {
    "credits": 8
  },
  "error": null,
  "resource_id": null,
  "provider_request_id": null,
  "team_id": null,
  "project_id": null,
  "created_at": "2026-03-25T12:00:00.000Z",
  "updated_at": "2026-03-25T12:00:02.000Z",
  "completed_at": "2026-03-25T12:00:02.000Z"
}

Request Example Body

{
  "prompt": "Confident electronic music bed for a product launch video",
  "model": "music.elevenlabs_music_v2_5",
  "duration_seconds": 30,
  "include_details": false
}
POST /audio/sfx
audio:write

Generate sound effects

Generate short sound effects or foley-style audio from a text prompt.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Body Fields

prompt string required

The sound effect prompt.

model string optional

Optional sound effect model alias from `/models`.

Default: sfx.elevenlabs_v1

duration_seconds number optional

Optional desired duration in seconds.

output_format string optional

Optional output format when supported by the backing provider.

prompt_influence number optional

Optional prompt influence value when supported.

loop boolean optional

Whether to generate a loop-friendly result when supported.

team_id string optional

Bill this run to a WORKSPACE (team) credit pool instead of your balance (`GET /v1/teams`). Membership and a spending role are asserted before the run is created — 404 `invalid_team_id` when you are not a member, 403 `team_role_cannot_spend` when your role cannot spend. Requires the `teams:read` scope. A route that cannot bill a pool yet returns 400 `team_billing_unsupported` rather than quietly charging you. The run echoes it back as `team_id`.

project_id string optional

File this run's output into one of your PROJECTS (`GET /v1/projects`), exactly as if you had the project open in the studio. Edit access is checked before the run is created. Routes whose output is not an image, video or audio a project can hold return 400 `project_filing_unsupported`. The run echoes it back as `project_id`, and `GET /v1/runs?project_id=` lists everything made inside it.

quote_token string optional

A signed price quote from the matching estimate endpoint, binding this submit to the price you were shown. May also be sent as the `X-Genfire-Quote` header. The token carries the quoted inputs, so it is only spendable on the request it was issued for: 409 `quote_expired` past its 10-minute life, 409 `quote_mismatch` when the inputs or the price no longer match, 401 `quote_invalid` when the signature does not verify. Every 409 carries a FRESH quote in the problem body so you can resubmit in one step.

curl Example

curl https://api.genfire.ai/v1/audio/sfx \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "prompt": "Heavy metal door slam in a large warehouse",
  "model": "sfx.elevenlabs_v1",
  "duration_seconds": 4
}'

Response Example

{
  "id": "run_sfx_123",
  "object": "run",
  "status": "completed",
  "capability": "sound_effect_generation",
  "endpoint": "audio.sfx.create",
  "model": "sfx.elevenlabs_v1",
  "request_id": "run_img_123",
  "input_summary": {
    "prompt": "Studio product photo of a matte black water bottle",
    "model": "image.nano_banana_2",
    "aspect_ratio": "1:1",
    "count": 1
  },
  "output": {
    "audio_id": "audio_sfx_123",
    "audio_url": "https://cdn.genfire.ai/output/sfx-123.mp3",
    "duration": 4.1
  },
  "usage": {
    "credits": 8
  },
  "error": null,
  "resource_id": null,
  "provider_request_id": null,
  "team_id": null,
  "project_id": null,
  "created_at": "2026-03-25T12:00:00.000Z",
  "updated_at": "2026-03-25T12:00:02.000Z",
  "completed_at": "2026-03-25T12:00:02.000Z"
}

Request Example Body

{
  "prompt": "Heavy metal door slam in a large warehouse",
  "model": "sfx.elevenlabs_v1",
  "duration_seconds": 4
}
POST /audio/voice-conversions
audio:write

Convert a recording into another voice

Speech-to-speech: keep the performance — the timing, the emphasis, the pauses — and change only the voice. Use this instead of `/audio/speech` when you already have a take worth keeping. ElevenLabs voices only (a cloned `clone_*` voice has no speech-to-speech equivalent). Video sources have their audio track extracted first. Synchronous, and billed per second of SOURCE audio, measured server-side.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Body Fields

audio_url string required

Absolute URL of the source recording (audio or video, up to 5 minutes). Your own `POST /v1/uploads` asset URLs work as-is.

voice_id string required

The ElevenLabs voice to convert INTO. List options via `/audio/voices?include=stock`.

model string optional

Speech-to-speech model id.

Default: eleven_multilingual_sts_v2

remove_background_noise boolean optional

Strip background noise from the source before converting.

Default: false

project_id string optional

File the converted audio into one of your PROJECTS (`GET /v1/projects`) when the run completes.

title string optional

Title for the converted audio in your library.

team_id string optional

Bill this run to a WORKSPACE (team) credit pool instead of your balance (`GET /v1/teams`). Membership and a spending role are asserted before the run is created — 404 `invalid_team_id` when you are not a member, 403 `team_role_cannot_spend` when your role cannot spend. Requires the `teams:read` scope. A route that cannot bill a pool yet returns 400 `team_billing_unsupported` rather than quietly charging you. The run echoes it back as `team_id`.

curl Example

curl https://api.genfire.ai/v1/audio/voice-conversions \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "audio_url": "https://cdn.genfire.ai/uploads/raw-read.mp3",
  "voice_id": "voice_abc123",
  "model": "eleven_multilingual_sts_v2"
}'

Response Example

{
  "id": "run_voice_conversion_123",
  "object": "run",
  "status": "completed",
  "capability": "voice_conversion",
  "endpoint": "audio.voice_conversions.create",
  "model": "eleven_multilingual_sts_v2",
  "request_id": "run_img_123",
  "input_summary": {
    "prompt": "Studio product photo of a matte black water bottle",
    "model": "image.nano_banana_2",
    "aspect_ratio": "1:1",
    "count": 1
  },
  "output": {
    "audio_url": "https://firebasestorage.googleapis.com/.../voice_changer_123.mp3",
    "duration_seconds": 18.4,
    "voice_id": "voice_abc123",
    "model": "eleven_multilingual_sts_v2"
  },
  "usage": {
    "credits": 8
  },
  "error": null,
  "resource_id": null,
  "provider_request_id": null,
  "team_id": null,
  "project_id": null,
  "created_at": "2026-03-25T12:00:00.000Z",
  "updated_at": "2026-03-25T12:00:02.000Z",
  "completed_at": "2026-03-25T12:00:02.000Z"
}

Request Example Body

{
  "audio_url": "https://cdn.genfire.ai/uploads/raw-read.mp3",
  "voice_id": "voice_abc123",
  "model": "eleven_multilingual_sts_v2"
}

Notes

  • Source audio is capped at 5 minutes. A longer source is rejected with 400 before anything is charged.
  • Billed per ceil(second) of the SOURCE, measured with ffprobe — not from the file size and not from a duration you send.
  • ElevenLabs voices only: cloned (`clone_*`) and template voice ids are rejected with 400.
POST /audio/transcriptions
audio:write

Transcribe audio or video (speech-to-text)

Transcribe speech to text with word- and segment-level timestamps. Two engines: OpenAI Whisper (default) and ElevenLabs Scribe v2 (`transcription.elevenlabs_scribe_v2` — 90+ languages, speaker diarization with `speaker_id` on every word, keyterm biasing, entity detection/redaction, filler removal). Provide exactly one of `audio_url`, `video_url`, or `youtube_url`. Video sources have their audio extracted automatically. Billed per second of media duration.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Body Fields

audio_url string optional

URL of a direct audio file (mp3, wav, m4a, aac, ogg, flac, opus, webm, wma). One of audio_url / video_url / youtube_url is required.

video_url string optional

URL of a direct video file; audio is extracted before transcription.

youtube_url string optional

A YouTube URL to download and transcribe (max 2 hours).

model string optional

Optional transcription model alias from `/models`: `transcription.whisper_v1` or `transcription.elevenlabs_scribe_v2`.

Default: transcription.whisper_v1

language string optional

Scribe only — ISO 639-1/639-3 code of the spoken language. Omit to auto-detect.

diarize boolean optional

Scribe only — label speakers; every word carries `speaker_id` and segments break on speaker change.

Default: true

num_speakers integer optional

Scribe only — expected speaker count, 1–32. Omit for automatic.

keyterms string[] optional

Scribe only — domain vocabulary (names, products, jargon) to bias recognition towards. Up to 1000 terms, <50 chars each. Adds a 30% premium upstream.

tag_audio_events boolean optional

Scribe only — emit `[laughter]`, `[applause]` etc. as `type: "audio_event"` words.

Default: true

no_verbatim boolean optional

Scribe only — drop fillers and disfluencies ("um", repeated words) for a clean read.

entity_detection string | string[] optional

Scribe only — detect entities: `all`, `pii`, `phi`, `pci`, `other`, `offensive_language`. Results land in `output.entities`.

entity_redaction string | string[] optional

Scribe only — redact the listed entity classes (subset of `entity_detection`) in the transcript text.

detect_speaker_roles boolean optional

Scribe only — label speaker roles (e.g. agent / customer) where detectable.

team_id string optional

Bill this run to a WORKSPACE (team) credit pool instead of your balance (`GET /v1/teams`). Membership and a spending role are asserted before the run is created — 404 `invalid_team_id` when you are not a member, 403 `team_role_cannot_spend` when your role cannot spend. Requires the `teams:read` scope. A route that cannot bill a pool yet returns 400 `team_billing_unsupported` rather than quietly charging you. The run echoes it back as `team_id`.

project_id string optional

File this run's output into one of your PROJECTS (`GET /v1/projects`), exactly as if you had the project open in the studio. Edit access is checked before the run is created. Routes whose output is not an image, video or audio a project can hold return 400 `project_filing_unsupported`. The run echoes it back as `project_id`, and `GET /v1/runs?project_id=` lists everything made inside it.

quote_token string optional

A signed price quote from the matching estimate endpoint, binding this submit to the price you were shown. May also be sent as the `X-Genfire-Quote` header. The token carries the quoted inputs, so it is only spendable on the request it was issued for: 409 `quote_expired` past its 10-minute life, 409 `quote_mismatch` when the inputs or the price no longer match, 401 `quote_invalid` when the signature does not verify. Every 409 carries a FRESH quote in the problem body so you can resubmit in one step.

curl Example

curl https://api.genfire.ai/v1/audio/transcriptions \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "audio_url": "https://cdn.genfire.ai/uploads/interview.mp3",
  "model": "transcription.whisper_v1"
}'

Response Example

{
  "id": "run_transcription_123",
  "object": "run",
  "status": "completed",
  "capability": "transcription",
  "endpoint": "audio.transcriptions.create",
  "model": "transcription.whisper_v1",
  "request_id": "run_img_123",
  "input_summary": {
    "prompt": "Studio product photo of a matte black water bottle",
    "model": "image.nano_banana_2",
    "aspect_ratio": "1:1",
    "count": 1
  },
  "output": {
    "transcript_id": "audio_txn_123",
    "text": "Welcome to the Genfire creative workflow.",
    "language": "en",
    "duration": 3.2,
    "words": [
      {
        "word": "Welcome",
        "start": 0,
        "end": 0.4,
        "probability": 1
      }
    ],
    "segments": [
      {
        "id": 0,
        "start": 0,
        "end": 3.2,
        "text": "Welcome to the Genfire creative workflow."
      }
    ],
    "audio_url": "https://storage.googleapis.com/genfire/extracted-audio/123.mp3"
  },
  "usage": {
    "credits": 8
  },
  "error": null,
  "resource_id": null,
  "provider_request_id": null,
  "team_id": null,
  "project_id": null,
  "created_at": "2026-03-25T12:00:00.000Z",
  "updated_at": "2026-03-25T12:00:02.000Z",
  "completed_at": "2026-03-25T12:00:02.000Z"
}

Request Example Body

{
  "audio_url": "https://cdn.genfire.ai/uploads/interview.mp3",
  "model": "transcription.whisper_v1"
}

Notes

  • Max media size 500MB; max duration 2 hours.
  • Scribe v2 words carry `speaker_id` when diarized and `type: "audio_event"` for tagged non-speech sounds; Whisper words carry `probability` instead.
  • Async — usually returns a queued run first; poll the run until `status` is `completed`, then read `output.text` / `output.words` / `output.segments`.
  • YouTube transcription is supported but downloads third-party content — ensure your use complies with the source platform’s terms.
POST /products/extract
products:write

Extract product data from a URL

Scrape product details from a public product page and return the result inside a run envelope.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Body Fields

url string required

A public product page URL. Private, local, and unsafe targets are blocked.

curl Example

curl https://api.genfire.ai/v1/products/extract \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://example.com/products/matte-black-water-bottle"
}'

Response Example

{
  "id": "run_product_123",
  "object": "run",
  "status": "completed",
  "capability": "product_extraction",
  "endpoint": "products.extract.create",
  "model": null,
  "request_id": "run_img_123",
  "input_summary": {
    "prompt": "Studio product photo of a matte black water bottle",
    "model": "image.nano_banana_2",
    "aspect_ratio": "1:1",
    "count": 1
  },
  "output": {
    "product": {
      "success": true,
      "title": "Matte Black Water Bottle",
      "description": "Insulated stainless steel water bottle",
      "category": "hydration",
      "price": "$39",
      "images": [
        "https://example.com/product.jpg"
      ]
    },
    "cached": false
  },
  "usage": {
    "credits": 8
  },
  "error": null,
  "resource_id": null,
  "provider_request_id": null,
  "team_id": null,
  "project_id": null,
  "created_at": "2026-03-25T12:00:00.000Z",
  "updated_at": "2026-03-25T12:00:02.000Z",
  "completed_at": "2026-03-25T12:00:02.000Z"
}

Request Example Body

{
  "url": "https://example.com/products/matte-black-water-bottle"
}

Notes

  • Unsafe or private-network targets are rejected before scraping begins.
POST /models/3d/generations
images:write

Generate a 3D model from images

Turn one photo — or up to four of the SAME object from different angles — into a textured 3D mesh (GLB / FBX / OBJ / USDZ). Multiple angles route to the multi-image endpoint and produce noticeably better geometry than a single view. Async and long: the POST returns a `queued` run and the provider takes roughly 5-10 minutes. Poll `GET /v1/runs/{id}` until `completed`, then read `output.model_urls`. What you enable is what you pay for. Texturing, PBR maps and rigging each move the run onto a different credit key, in that order of precedence — a rigged model is billed as rigged whether or not it is also textured. Price a configuration first with `POST /v1/models/estimate-cost`.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Body Fields

image_url string optional

Single source image (absolute https URL or an upload `asset_url`). Provide this OR `image_urls`.

image_urls string[] optional

1-4 images of the SAME object from different angles. More than one routes to the multi-image endpoint. More than four is 400 `too_many_images`.

model string optional

A `model_3d_generation` alias from `GET /v1/models` — `3d.meshy_v7` (default) or `3d.meshy_v6`.

should_texture boolean optional

Generate surface textures.

Default: true

enable_pbr boolean optional

Also produce physically-based rendering maps (metallic / roughness / normal).

Default: false

enable_rigging boolean optional

Produce a rigged skeleton. Highest-priced variant, and it takes precedence over the PBR and texture keys.

Default: false

enable_animation boolean optional

Animate the rigged mesh. Requires `enable_rigging`.

Default: false

animation_action_id number optional

Which library action to animate, 0-696. Requires `enable_animation`; out of range is 400 `invalid_animation_action_id`.

rigging_height_meters number optional

Real-world height in metres, used to scale the rig.

topology string optional

"quad" or "triangle".

target_polycount number optional

Target polygon count, 100-300000. Out of range is 400 `invalid_target_polycount`.

model_type string optional

"standard" or "lowpoly".

ultra_mode boolean optional

Higher-fidelity geometry. Meshy v7 with a SINGLE image only; silently dropped for v6 and for multi-image requests.

should_remesh boolean optional

Remesh the generated geometry.

pose_mode string optional

"a-pose", "t-pose", or "" to leave the pose alone.

symmetry_mode string optional

"off", "auto" or "on".

texture_prompt string optional

Steer the texturing pass (with `should_texture`).

team_id string optional

Bill a workspace (team) credit pool instead of the personal balance. Requires the `teams:read` scope and a spending role; an insufficient pool is 402 `team_pool_insufficient`.

quote_token string optional

A `quote_token` from `POST /v1/models/estimate-cost`, to be charged the price you were quoted. May also be sent as the `X-Genfire-Quote` header.

curl Example

curl https://api.genfire.ai/v1/models/3d/generations \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "image_urls": [
    "https://example.com/chair-front.png",
    "https://example.com/chair-side.png"
  ],
  "model": "3d.meshy_v7",
  "should_texture": true,
  "enable_pbr": true
}'

Response Example

{
  "id": "run_3d_9f2c8b1a",
  "object": "run",
  "status": "queued",
  "capability": "model_3d_generation",
  "endpoint": "models.3d.generations.create",
  "model": "3d.meshy_v7",
  "output": null,
  "error": null,
  "resource_id": "model3d_7a1b2c3d",
  "created_at": "2026-09-12T10:00:00.000Z"
}

Request Example Body

{
  "image_urls": [
    "https://example.com/chair-front.png",
    "https://example.com/chair-side.png"
  ],
  "model": "3d.meshy_v7",
  "should_texture": true,
  "enable_pbr": true
}

Notes

  • Async: poll `GET /v1/runs/{id}`. A completed run carries `output.model_urls` with the per-format download links.
  • `project_id` is NOT accepted here — a mesh is not an image, video or audio a project can hold, so naming one is 400 `project_filing_unsupported`.
  • Replaying the same `Idempotency-Key` returns the first run (200 instead of 201) rather than generating and billing twice.

Reference

Marketing Studio

POST /marketing/products
marketing:read

Store a product from a URL

Scrape a product page and STORE the result on one of your brands in a single call, returning a product you can immediately reference as `product_id` when generating. `POST /v1/products/extract` scrapes the same page but only returns the data — nothing persists, so its result cannot fill a template slot. The product lands through the same path as every other add, so its images are mirrored into Genfire storage rather than left hotlinking the source store.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

brand_id string required

The brand to store the product on (`GET /v1/brands`). Must be a brand you own.

url string required

The product page to read.

title string optional

Override the scraped product name. Required when the page yields none.

description string optional

Override the scraped description.

curl Example

curl https://api.genfire.ai/v1/marketing/products \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "brand_id": "brand_abc123",
  "url": "https://acme.com/products/vitamin-c-serum"
}'

Response Example

{
  "id": "prod_abc",
  "object": "marketing_product",
  "name": "Vitamin C Serum",
  "brand_id": "brand_abc123",
  "image_url": "https://storage.googleapis.com/...",
  "images": [
    "https://storage.googleapis.com/..."
  ],
  "price": "$28",
  "source_url": "https://acme.com/products/vitamin-c-serum",
  "source": "web",
  "extraction_source": "metadata",
  "cached": false
}

Request Example Body

{
  "brand_id": "brand_abc123",
  "url": "https://acme.com/products/vitamin-c-serum"
}

Notes

  • Also requires `brands:write` — the product is stored on a brand.
  • 422 `product_extraction_failed` when the page yields no product; pass the fields directly to `POST /v1/brands/{brandId}/products` instead.
  • Shares the 24h scrape cache with `POST /v1/products/extract`.
GET /marketing/templates
marketing:read

Browse the ad-template library

Returns the curated Marketing Studio library — finished ads (product shots, motion, UGC, paid-social stills, posters, marketplace imagery) each carrying the RECIPE that produced it. A recipe is not just a prompt: it declares which typed reference slots (product / avatar) a caller must fill and in what ORDER the composed prompt cites them, which is what makes a template recreatable against your own product rather than merely inspiring. Run one by passing its id as `marketing_template_id` to `POST /v1/images/generations` or `POST /v1/videos/generations`. `search` matches titles, descriptions and the creative direction itself, so terms like "unboxing" or "marble counter" narrow the library.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

search query optional

Free-text match over title, description, ad format and the recipe prompt. Every whitespace-separated term must appear, so extra words narrow the result.

ad_format query optional

One of `product-shot`, `motion`, `ugc`, `ads`, `posters`, `marketplace`. See `GET /v1/marketing/ad-formats`.

media_type query optional

`image` or `video`. An image template runs through `/images/generations`; a video template through `/videos/generations`.

requires_avatar query optional

Set `true` for templates with a person in frame (those offering or requiring an avatar slot).

limit query optional

Page size, 1–100.

Default: 24

cursor query optional

`next_cursor` from the previous page.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/marketing/templates \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "razorcandy-ugc-3",
      "object": "marketing_template",
      "title": "Flash-lit hand hold",
      "description": "",
      "label": "UGC",
      "ad_format": "ugc",
      "media_type": "image",
      "media_url": "https://storage.googleapis.com/...",
      "thumbnail_url": "https://storage.googleapis.com/...",
      "aspect_ratio": "4:5",
      "featured": true,
      "recipe": {
        "mode": "image",
        "model": "image.nano_banana_pro",
        "prompt": "the person in image 2 holding {{product}} up to the camera, hard direct phone flash...",
        "aspect_ratio": "4:5",
        "resolution": "2K",
        "duration": null,
        "slots": {
          "product": "required",
          "avatar": "optional"
        },
        "slot_order": [
          "product",
          "avatar"
        ],
        "slot_binding": null,
        "required_inputs": [
          "product_image_url"
        ],
        "optional_inputs": [
          "avatar_image_url"
        ]
      },
      "created_at": "2026-08-18T12:00:00.000Z"
    }
  ],
  "has_more": true,
  "next_cursor": "razorcandy-ugc-3"
}

Notes

  • Read `recipe.required_inputs` before generating — it names the exact request fields the run is rejected without.
  • Do NOT copy `recipe.prompt` into your own `prompt`. Pass `marketing_template_id` and the server composes it, substituting the slot tokens with the positions your references are actually sent in. A `prompt` you send alongside is ADDITIVE — say what to change.
GET /marketing/templates/{templateId}
marketing:read

Get one ad template

Returns one template with its full recipe, plus up to 12 same-format siblings for "more like this".

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

templateId path required

Template id from `GET /v1/marketing/templates`.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/marketing/templates/{templateId} \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "id": "razorcandy-ugc-3",
  "object": "marketing_template",
  "ad_format": "ugc",
  "media_type": "image",
  "recipe": {
    "mode": "image",
    "model": "image.nano_banana_pro",
    "required_inputs": [
      "product_image_url"
    ],
    "optional_inputs": [
      "avatar_image_url"
    ]
  },
  "siblings": [
    {
      "id": "thirstcult-ugc-1",
      "object": "marketing_template",
      "ad_format": "ugc"
    }
  ]
}

Notes

  • 404 `marketing_template_not_found` if the id does not exist.
GET /marketing/ad-formats
marketing:read

List the ad formats

The six kinds of ad the library is filed under, each with what it is for, the media it produces, the slot shape it implies, and a live count of how many templates it holds.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/marketing/ad-formats \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "ugc",
      "object": "marketing_ad_format",
      "label": "UGC",
      "description": "Creator-style content shot to look like a real phone, not a studio...",
      "media_types": [
        "image",
        "video"
      ],
      "typical_slots": "product required, avatar required or optional",
      "template_count": 119
    }
  ]
}
GET /marketing/formats
marketing:read

List the creative formats (genres)

The GENRE — what kind of piece this is. Video genres: unboxing, ASMR unboxing, reboxing, try-on, tutorial, before-after, mess-to-fresh, crush test, mystery box, POV, giant product, hyper motion, selfie testimonial, direct-to-camera, secret hack, couple at home, TV spot, obsession, product showcase. Still-ad genres: headline, special offer, customer quote, star review, key features, benefits checklist, comparison, then-vs-now, social proof, comment, stat surround, bundle, why-we-are-different, behind the product, personal note, magazine, colour block, bold statement, whiteboard explainer, UGC side-by-side, organic post, scroll break. Pass an id as `marketing_format_id` on either generate endpoint. Composes ONTO a template, or stands alone with your own prompt.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

search query optional

Match over id, label, description and the fragment itself.

mode query optional

`image` or `video` — several genres are one or the other only.

ad_format query optional

Only genres most at home on this shelf. A hint, never a restriction.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/marketing/formats \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "unboxing-asmr",
      "object": "marketing_format",
      "label": "ASMR unboxing",
      "description": "Unboxing shot for texture and sound rather than speech.",
      "modes": [
        "video"
      ],
      "suits": [
        "ugc",
        "motion"
      ],
      "prompt_fragment": "Shoot this as an ASMR unboxing: no talking, very close framing on hands and materials..."
    }
  ]
}

Notes

  • A FORMAT is not a HOOK. A format describes the whole piece; a hook occupies only its opening beat. They are independent axes precisely so a stunt opening can ride on a tutorial.
GET /marketing/hooks
marketing:read

List the opening hooks

The OPENING MECHANIC — what physically happens in the first two seconds to stop a scroll. These are events, not copy angles: the product flying at the creator who ducks and comes up holding it, an impossible indoor blizzard that leaves the product untouched, gravity failing, the camera operator knocking into the creator, a silence held a beat too long. Each is typed `stunt` (breaks physics or plausibility) or `subtle` (stays believable and relies on human reaction). Every fragment hands off to whatever genre follows, so any hook composes with any format. Pass an id as `marketing_hook_id` on either generate endpoint.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

search query optional

Match over id, label, description, type and the fragment itself.

mode query optional

`image` or `video`. Most hooks are video-first — an opening beat needs time to happen.

type query optional

`stunt` or `subtle`.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/marketing/hooks \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "product-dodge",
      "object": "marketing_hook",
      "label": "Product dodge",
      "description": "The product flies at the creator, who ducks and comes up holding it.",
      "type": "stunt",
      "modes": [
        "video"
      ],
      "prompt_fragment": "Open on a stunt: the product flies in fast from off-camera straight at the person..."
    }
  ]
}
GET /marketing/settings
marketing:read

List the scene settings

The ENVIRONMENT a shot lives in, in two categories. `realistic` places the piece somewhere real — marble kitchen, bathroom vanity, bedroom, gym floor, desk, golden hour, city street, car interior, retail shelf. `unrealistic` is scroll-stopping by location alone: sitting on an airliner wing mid-flight, at the rim of an active volcano, shrunk to doll size beside a full-size product, on the roof of a moving car — always directed to be played completely straight. Each fragment names its light as well as its surfaces, because an environment without a light source renders as a composite rather than a photograph. Pass an id as `marketing_setting_id` on either generate endpoint.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

search query optional

Match over id, label, description, category and the fragment itself.

category query optional

`realistic` or `unrealistic`.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/marketing/settings \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "marble-kitchen",
      "object": "marketing_setting",
      "label": "Marble kitchen",
      "description": "Bright modern kitchen, stone counters, daylight.",
      "category": "realistic",
      "prompt_fragment": "Relocate the scene to a bright modern kitchen..."
    },
    {
      "id": "volcano-rim",
      "object": "marketing_setting",
      "label": "Volcano rim",
      "description": "Beside active lava, unhurried.",
      "category": "unrealistic",
      "prompt_fragment": "Relocate the scene to the rim of an active volcano..."
    }
  ]
}
GET /marketing/avatars
marketing:read

List avatars you can put in an ad

One list over two sources: your own trained influencers AND the house avatar library. Both fill the same avatar slot, so they are returned together (influencers first) with `source` saying which is which. An influencer row also carries `influencer_id` — prefer those when the same face has to recur across many pieces, because identity binds through the influencer's conditioning photos rather than resemblance to one image. Pass an `id` as `avatar_id` on either generate endpoint.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

search query optional

Match over name, handle and the scene/gender/age tags.

source query optional

`user` for your trained influencers only, `preset` for the house library only. Omit for both.

scene query optional

House-library scene tag: ugc, car, studio, podcast, interview, lifestyle, office, outdoor.

gender query optional

male, female, or neutral (house library only).

age query optional

young-adult, adult, middle-aged, or senior (house library only).

limit query optional

Page size, 1–100.

Default: 24

cursor query optional

`next_cursor` from the previous page.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/marketing/avatars \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "influencer:abc123",
      "object": "marketing_avatar",
      "source": "influencer",
      "name": "Genny",
      "handle": "genny",
      "image_url": "https://storage.googleapis.com/...",
      "status": "ready",
      "influencer_id": "abc123",
      "scenes": [],
      "gender": null,
      "age": null
    },
    {
      "id": "preset:tpl_9f",
      "object": "marketing_avatar",
      "source": "preset",
      "name": "Studio creator",
      "handle": null,
      "image_url": "https://storage.googleapis.com/...",
      "status": "ready",
      "influencer_id": null,
      "scenes": [
        "ugc"
      ],
      "gender": "female",
      "age": "young-adult"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Notes

  • Avatars without an image are omitted — they cannot fill a slot.
GET /marketing/products
marketing:read

List your stored products

Every product on every brand you own, flattened — "which product is this ad for?" is not a per-brand question. `source` is `web` when the product was scraped from the brand's own site and `upload` when it was added directly. Pass an `id` as `product_id` on either generate endpoint to fill a template's product slot without re-uploading anything.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

search query optional

Match over product name, description, brand name and USPs.

brand_id query optional

Restrict to one brand (`GET /v1/brands`).

limit query optional

Page size, 1–100.

Default: 24

cursor query optional

`next_cursor` from the previous page.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/marketing/products \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "prod_abc",
      "object": "marketing_product",
      "name": "Vitamin C Serum",
      "description": "30ml brightening serum",
      "brand_id": "brand_abc123",
      "brand_name": "Acme Skin",
      "image_url": "https://storage.googleapis.com/...",
      "images": [
        "https://storage.googleapis.com/..."
      ],
      "price": "$28",
      "source_url": "https://acme.com/serum",
      "source": "web",
      "usps": [
        "Vegan",
        "Fragrance-free"
      ]
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Notes

  • Requires `brands:read` in addition to `marketing:read` — the products come from the brand store.
  • Products with no image are omitted; they cannot fill a product slot.
  • Empty list? Ingest a brand first with `POST /v1/brands/ingestions`, or pass a photo directly as `product_image_url`.

Reference

Games

POST /games/generations
images:write

Generate a browser game

Build a fully-playable, self-contained HTML browser game (Phaser / canvas / Three.js) from a text prompt. The generated game is hosted automatically; once the run completes its `play_url` is public and anyone can open it — no install. Iterate on an existing game by passing its `game_id` with a change prompt. Async — returns a queued run immediately and the codegen runs on a worker (usually 1–3 min); poll `GET /runs/{id}` until `status` is `completed`, then read `output.play_url`.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Body Fields

prompt string required

What game to build — or, with `game_id`, the change to make (e.g. "make it faster, add a boss").

game_id string optional

Iterate on an existing game: pass the `game_id` from a prior generation. The stored game is re-generated in place at the same `play_url`.

model string optional

Codegen model alias (e.g. `claude-fable-5-1`, `claude-opus-5-5`, `claude-sonnet-5`, `gpt-6-sol`). Defaults to `claude-fable-5-1`.

asset_urls array optional

Up to 16 asset URLs (images / GLB / audio from other Genfire endpoints) to wire into the game as sprites, textures, or sound.

multiplayer boolean optional

Build with realtime multiplayer via the Genfire relay. Players join with the same `?room=` link.

Default: false

curl Example

curl https://api.genfire.ai/v1/games/generations \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "prompt": "A neon endless-runner where you dodge obstacles and collect coins",
  "multiplayer": false
}'

Response Example

{
  "id": "run_game_123",
  "object": "run",
  "status": "queued",
  "capability": "game_generation",
  "endpoint": "games.generations.create",
  "model": "game.genfire_v1",
  "request_id": "run_game_123",
  "input_summary": {
    "prompt": "A neon endless-runner where you dodge obstacles and collect coins",
    "iterate": false
  },
  "output": null,
  "usage": null,
  "error": null,
  "resource_id": "game_123",
  "provider_request_id": null,
  "created_at": "2026-03-25T12:00:00.000Z",
  "updated_at": "2026-03-25T12:00:00.000Z",
  "completed_at": null
}

Request Example Body

{
  "prompt": "A neon endless-runner where you dodge obstacles and collect coins",
  "multiplayer": false
}

Notes

  • Guarded by `images:write` — game generation also produces a cover thumbnail via image generation.
  • Async: the POST returns a `queued` run with `output: null`. Poll `GET /runs/{id}` until `status` is `completed`; the run output then contains `game_id`, `play_url`, `thumbnail_url`, and `title`. The `play_url` is shareable as soon as it appears; use `POST /games/{gameId}/publish` to also list it in the public gallery.
POST /games/{gameId}/publish
images:write

Publish or unpublish a game

Publish a completed game you own to the public Genfire games gallery (genfire.ai/games), where anyone can discover and play it. Pass `{ "publish": false }` to remove it from the gallery again. The game's `play_url` is shareable whether or not it is published — publishing only controls the public marketplace listing.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

gameId path required

The `game_id` of a completed game from `POST /games/generations`.

Body Fields

publish boolean optional

true (default) to publish to the public gallery; false to unpublish.

Default: true

curl Example

curl https://api.genfire.ai/v1/games/{gameId}/publish \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "publish": true
}'

Response Example

{
  "id": "game_abc123",
  "object": "game",
  "is_public": true
}

Request Example Body

{
  "publish": true
}

Notes

  • Returns 404 if the game does not exist, is not owned by the authenticated account, or is not in a completed state.
POST /games/deployments
images:write

Deploy a game you wrote

The counterpart to `POST /v1/games/generations`: that route WRITES the game for you, this one ships one you already wrote. No codegen — send the finished playable document and it is hosted. One complete self-contained HTML game (canvas / Phaser / Three.js via CDN), smoke-tested in a real headless browser before any credits move. Share the returned `play_url`, never a storage URL. Redeploy in place by passing a previous `game_id`. Set `multiplayer: true` and the Genfire relay client is injected into your HTML at deploy time (before the smoke test) — code against the global `GenfireMP` rather than writing your own transport, and keep the game fully playable solo, because a player who opens the link with no `?room=` gets `GenfireMP.enabled() === false`. Synchronous: the run comes back already `completed`.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Body Fields

html string required

The complete single-file game. Must start with `<!DOCTYPE html>` or `<html>` and end with `</html>`; 200 bytes to 1,500,000 bytes.

title string optional

Name in the gallery and the library, trimmed to 80 chars.

Default: the document's own <title>, else the first 60 chars of `brief`, else "Deployed game"

brief string optional

What the game is, max 2000 chars. Stored as the prompt and used for the thumbnail.

game_id string optional

Redeploy a game you already own, in place at the same `play_url`. A game you do not own is 404 `game_not_found`.

multiplayer boolean optional

Inject the Genfire multiplayer relay client before hosting. Players co-play by opening the play link with the same `?room=` value.

Default: false

team_id string optional

Bill a workspace (team) credit pool instead of the personal balance. Requires the `teams:read` scope and a spending role.

curl Example

curl https://api.genfire.ai/v1/games/deployments \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "title": "Neon Runner",
  "brief": "An endless runner with neon parallax",
  "multiplayer": false,
  "html": "<!DOCTYPE html><html>…</html>"
}'

Response Example

{
  "id": "run_game_8d2e3f4a",
  "object": "run",
  "status": "completed",
  "capability": "game_generation",
  "endpoint": "games.deployments.create",
  "model": null,
  "output": {
    "game_id": "game_1a2b3c4d",
    "play_url": "https://genfire.ai/games/game_1a2b3c4d/play",
    "thumbnail_url": "https://cdn.genfire.ai/games/game_1a2b3c4d/thumb.png",
    "title": "Neon Runner"
  },
  "resource_id": "game_1a2b3c4d",
  "created_at": "2026-09-12T10:00:00.000Z",
  "completed_at": "2026-09-12T10:00:11.000Z"
}

Request Example Body

{
  "title": "Neon Runner",
  "brief": "An endless runner with neon parallax",
  "multiplayer": false,
  "html": "<!DOCTYPE html><html>…</html>"
}

Notes

  • The boot check runs BEFORE billing: a 422 `boot_failed` costs nothing. Fix the error and retry with a NEW `Idempotency-Key`.
  • Flat price (5 credits by default) regardless of document size.
  • `project_id` is NOT accepted — naming one is 400 `project_filing_unsupported`.
  • Publishing to the public gallery is a separate call: `POST /v1/games/{gameId}/publish`.

Reference

Runs

GET /runs
runs:read

List recent runs

Lists recent runs for the authenticated account. Use this to inspect async activity across generation endpoints and workflows.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

status query optional

Optional run status filter such as `queued`, `processing`, `completed`, or `failed`.

capability query optional

Optional capability filter such as `image_generation` or `workflow.hook_pack`.

app query optional

Optional in-product surface filter — e.g. `marketing-studio`. Returns the runs made in that surface from BOTH origins: pieces created in the Genfire dashboard AND ones created through this API with the surface's own parameters (a `marketing_template_id` run tags itself the same way the dashboard bar does), so it is that surface's whole history rather than an API-only slice. Every run's own surface is echoed back as `app`. Runs that named no surface have `app: null` and are excluded whenever this filter is set. Uses the deep-scan path, so the response also carries `scanned` and `has_more`.

team_id query optional

Only runs billed to this WORKSPACE pool (`GET /v1/teams`). Narrows the listing to runs made through this API — a dashboard generation has no notion of which workspace paid for it.

project_id query optional

Only runs made inside this PROJECT (`GET /v1/projects`) — everything an agent produced while working in it, whatever the capability. Same narrowing as `team_id`.

limit query optional

Maximum number of runs to return.

Default: 25

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/runs \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "run_img_123",
      "object": "run",
      "status": "completed",
      "capability": "image_generation",
      "endpoint": "images.generations.create",
      "model": "image.nano_banana_2",
      "request_id": "run_img_123",
      "input_summary": {
        "prompt": "Studio product photo of a matte black water bottle",
        "model": "image.nano_banana_2",
        "aspect_ratio": "1:1",
        "count": 1
      },
      "output": {
        "images": [
          {
            "url": "https://cdn.genfire.ai/output/image-001.png"
          }
        ],
        "output_url": "https://cdn.genfire.ai/output/image-001.png"
      },
      "usage": {
        "credits": 8
      },
      "error": null,
      "resource_id": null,
      "provider_request_id": null,
      "team_id": null,
      "project_id": null,
      "created_at": "2026-03-25T12:00:00.000Z",
      "updated_at": "2026-03-25T12:00:02.000Z",
      "completed_at": "2026-03-25T12:00:02.000Z"
    },
    {
      "id": "run_vid_123",
      "object": "run",
      "status": "queued",
      "capability": "video_generation",
      "endpoint": "videos.generations.create",
      "model": "video.veo_3_1",
      "request_id": "run_vid_123",
      "input_summary": {
        "prompt": "Cinematic close-up of a luxury watch with soft lighting",
        "model": "video.veo_3_1",
        "aspect_ratio": "16:9",
        "duration": 8,
        "has_image": false,
        "generate_audio": true
      },
      "output": null,
      "usage": null,
      "error": null,
      "resource_id": "video_123",
      "provider_request_id": null,
      "created_at": "2026-03-25T12:00:00.000Z",
      "updated_at": "2026-03-25T12:00:00.000Z",
      "completed_at": null
    }
  ]
}
GET /runs/{runId}
runs:read

Get a run by id

Use the run id returned by a generation endpoint or workflow execution to inspect status, output, usage, and provider metadata. A completed VIDEO run's `output` carries `video_url`, `thumbnail_url`, `video_id` and — when available — `last_frame_url`: a still of the final frame at the video's resolution, for chaining the next shot (pass it as the next run's `image_url`).

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

runId path required

The run id returned by the API.

wait query optional

LONG-POLL: hold the request until the run reaches a terminal state (`completed` or `failed`) or this many seconds elapse, then return whatever state the run is in. Accepts 0–30 (values above 30 are capped, not rejected); omit or pass 0 for the default immediate response. One long-poll costs one request against your rate limit however long it is held, so `?wait=30` uses roughly a fifteenth of the requests a 2s polling loop would. A non-numeric value returns 400 `invalid_wait`.

Default: 0

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/runs/{runId} \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "id": "run_img_123",
  "object": "run",
  "status": "completed",
  "capability": "image_generation",
  "endpoint": "images.generations.create",
  "model": "image.nano_banana_2",
  "request_id": "run_img_123",
  "input_summary": {
    "prompt": "Studio product photo of a matte black water bottle",
    "model": "image.nano_banana_2",
    "aspect_ratio": "1:1",
    "count": 1
  },
  "output": {
    "images": [
      {
        "url": "https://cdn.genfire.ai/output/image-001.png"
      }
    ],
    "output_url": "https://cdn.genfire.ai/output/image-001.png"
  },
  "usage": {
    "credits": 8
  },
  "error": null,
  "resource_id": null,
  "provider_request_id": null,
  "team_id": null,
  "project_id": null,
  "created_at": "2026-03-25T12:00:00.000Z",
  "updated_at": "2026-03-25T12:00:02.000Z",
  "completed_at": "2026-03-25T12:00:02.000Z"
}
GET /runs/{runId}/output
runs:read

Get the output envelope for a run

Returns just the terminal output envelope for a run, which is useful when you only need the final payload. For a completed video this includes `last_frame_url` when available — a still of the final frame at the video's resolution, for chaining the next shot.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

runId path required

The run id returned by the API.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/runs/{runId}/output \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "id": "run_img_123",
  "object": "run_output",
  "status": "completed",
  "capability": "image_generation",
  "output": {
    "images": [
      {
        "url": "https://cdn.genfire.ai/output/image-001.png"
      }
    ],
    "output_url": "https://cdn.genfire.ai/output/image-001.png"
  },
  "error": null,
  "completed_at": "2026-03-25T12:00:02.000Z"
}

Reference

Genjudo

GET /videos/genjudo/presets
models:read

Genjudo: motion transfer & video edit recipes

Genfire Genjudo is motion transfer and video edit on the public API — something no competitor exposes outside their own app. Both halves are ONE call to `POST /v1/videos/generations` with `model: "video.seedance_2_5"` and a different `task`, and this endpoint is the recipe book for it: 25 edit recipes across six families (relight, swap, reframe, cleanup, restyle, draw) and 6 motion-transfer recipes, each with the prompt already written in the `@Video1` / `@Image1` citation idiom the model binds on. MOTION TRANSFER (`task: "reference"`): one clip in `reference_video_urls` supplies the MOTION, up to 30 stills in `reference_image_urls` supply who or what performs it. Leave `aspect_ratio` and `duration` off and the run MATCHES THE CLIP — its frame (`auto`) and its own measured length, which is also what bills; send a duration, or a ratio other than the clip's own, to set it yourself. VIDEO EDIT (`task: "editing"`): the clip in `reference_video_urls` is the thing being changed — re-lit, product swapped, background rebuilt — and any replacement subject rides in `reference_image_urls`. The output follows the SOURCE, so the model coerces `aspect_ratio` and `duration` to auto; do not send them. SOUND: on both, with one clip and neither `generate_audio` nor `keep_source_audio` sent, the result keeps the CLIP'S OWN sound (`keep_source_audio` defaults to true; not when `reference_audio_urls` is sent or the clip has no audio track) — send `generate_audio: true` (or `keep_source_audio: false`) for a generated soundtrack, `generate_audio: false` for silence. Citation is not optional: `@Video1` is the first `reference_video_urls` entry and `@Image1` the first `reference_image_urls` entry, and a prompt that never names them gives the model no reason to bind them. Every preset prompt here is already written that way — send it verbatim, or append your own sentence to it. FREE, cached, and house content; `coming_soon: true` presets (region-based "draw to edit") are listed with the flag rather than hidden.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

group query optional

Filter the edit list to one family: `relight`, `swap`, `reframe`, `cleanup`, `restyle` or `draw`. Anything else is a 400 `invalid_group`. The motion list is returned whole either way.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/videos/genjudo/presets \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "genjudo_presets",
  "model": "video.seedance_2_5",
  "edit_groups": [
    {
      "id": "relight",
      "label": "Light & weather",
      "blurb": "Change the light, keep the take"
    },
    {
      "id": "swap",
      "label": "Swap anything",
      "blurb": "Swap a product, outfit, face or place"
    }
  ],
  "edit_presets": [
    {
      "id": "golden-hour",
      "object": "genjudo_edit_preset",
      "label": "Golden hour",
      "group": "relight",
      "task": "editing",
      "prompt": "Relight @Video1 as late golden hour: warm low sun from camera left, long soft shadows falling to the right, amber rim light on edges and warm bounce filling the shadows. Keep the subject, motion, framing and timing exactly as filmed.",
      "requires_image": false,
      "restyle_preset_id": null,
      "coming_soon": false
    },
    {
      "id": "swap-product",
      "object": "genjudo_edit_preset",
      "label": "Swap the product",
      "group": "swap",
      "task": "editing",
      "prompt": "Replace the product shown in @Video1 with the product in @Image1. Match its scale, perspective, contact shadows and reflections to the shot, and keep the label and materials accurate. Keep the performance, hands, motion, lighting and framing exactly as filmed.",
      "requires_image": true,
      "restyle_preset_id": null,
      "coming_soon": false
    }
  ],
  "motion_presets": [
    {
      "id": "exact-transfer",
      "object": "genjudo_motion_preset",
      "label": "Exact transfer",
      "task": "reference",
      "prompt": "Transfer the motion in @Video1 onto @Image1 exactly: match the timing, camera move and framing, and keep the identity, clothing and proportions of @Image1 unchanged.",
      "images": 1
    }
  ]
}

Notes

  • End-to-end video edit: pick a preset (say `swap-product`), then POST /v1/videos/generations with `{ "model": "video.seedance_2_5", "task": "editing", "reference_video_urls": ["https://…/ad-cut.mp4"], "reference_image_urls": ["https://…/new-bottle.png"], "prompt": "<the preset prompt>" }`. Omit `aspect_ratio` and `duration` — an editing run follows the source clip.
  • End-to-end motion transfer: pick a motion preset (say `exact-transfer`), then POST /v1/videos/generations with `{ "model": "video.seedance_2_5", "task": "reference", "reference_video_urls": ["https://…/dance.mp4"], "reference_image_urls": ["https://…/character.png"], "prompt": "<the preset prompt>", "aspect_ratio": "9:16", "duration": 6 }`. Drop `aspect_ratio` and `duration` to match the clip instead (its frame and its measured length, billed on that length). `images` on each motion preset says how many stills its prompt cites (@Image1, @Image2).
  • `requires_image: true` means the preset prompt cites `@Image1` — send at least one `reference_image_urls` entry or the citation has nothing to resolve to.
  • Limits: up to 10 reference clips, each 2-30s, under 200 MB and 24-60 fps, and 30s COMBINED across the pool. `task: "editing"` and `task: "extension"` take exactly one clip, and an edit clip must be at least 4s. Send `reference_video_trims` to use only part of a clip; the combined budget is checked before any credits are reserved.
  • Pricing: 20 credits per second at 720p (`seedance_2_5_video_ref`) on the OUTPUT and again on the reference clip seconds (after any trim) for any reference run carrying at least one clip — which is every Genjudo run; a 15 s edit of a 15 s clip bills 30 seconds = 600 credits. Images-only reference is 47 per output second. 480p ×0.47, 1080p ×2.5. `POST /v1/models/estimate-cost` with the same body quotes it exactly (it measures the clip).
GET /videos/genjudo/motion-library
models:read

Genjudo: curated motion clips

The Motion Library — curated reference CLIPS whose motion can be transferred onto your own character or product. Pass an entry's `media_url` as the single `reference_video_urls` item of a `task: "reference"` run and your own subject as `reference_image_urls`. Each entry carries a suggested `prompt` already written in the `@Video1` / `@Image1` idiom; use it verbatim or as the base for your own. FREE, cached, house content. Returns an empty `data` array when nothing is published yet — never an error.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

limit query optional

Maximum entries to return (1-100).

Default: 60

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/videos/genjudo/motion-library \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "motion-lib-runway-walk",
      "object": "genjudo_motion",
      "title": "Runway walk, locked-off camera",
      "media_url": "https://cdn.genfire.ai/motion-library/runway-walk.mp4",
      "thumbnail_url": "https://cdn.genfire.ai/motion-library/runway-walk.jpg",
      "duration": 6,
      "aspect_ratio": "9:16",
      "tags": [
        "fashion",
        "walk",
        "full-body"
      ],
      "prompt": "Transfer the motion in @Video1 onto @Image1: the same walk, timing and camera framing, identity and wardrobe unchanged."
    }
  ]
}

Notes

  • `duration` is the clip length in seconds where known. It counts against the 30s combined reference-video budget, so pair a long motion clip with nothing else — or trim it with `reference_video_trims`.
  • Bring your own clip instead whenever you have one: nothing about `task: "reference"` requires a library entry.
GET /videos/genjudo/community
models:read

Genjudo: community runs to recreate

Community Trends — PUBLIC finished Genjudo runs (motion transfer and objects swap) that other people shared, each with the SOURCE clip it used, newest first. To recreate one on your own subject: `source_video_url` is the single `reference_video_urls` item (trimmed to `source_trim` with `reference_video_trims` when set), your own stills go in `reference_image_urls`, and `prompt` is what the run sent — `task: "reference"` for `kind: "motion"`, `task: "editing"` for `kind: "swap"`. `result_url` is the finished render. FREE. Paged by an opaque `cursor`: most public renders have no source clip, so each call scans a bounded number of them and a page can be short — even empty — while `next_cursor` is still set. Stop when `next_cursor` is null. No owner information is returned.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

limit query optional

Runs to return, 1-48 (larger values are clamped to 48).

Default: 12

cursor query optional

`next_cursor` from the previous page. Anything else is a 400 `invalid_cursor`.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/videos/genjudo/community \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "x7Kq2mVb9TfR3nLp0aZc",
      "object": "genjudo_community_run",
      "title": "Rooftop shuffle at golden hour",
      "kind": "motion",
      "source_video_url": "https://firebasestorage.googleapis.com/.../dance-source.mp4",
      "source_trim": {
        "start": 2.5,
        "end": 9.5
      },
      "result_url": "https://firebasestorage.googleapis.com/.../render.mp4",
      "thumbnail_url": "https://firebasestorage.googleapis.com/.../render.jpg",
      "duration": 7,
      "aspect_ratio": "9:16",
      "prompt": "Transfer the motion in @Video1 onto @Image1 exactly; keep the identity, clothing and proportions of @Image1.",
      "reference_image_urls": [
        "https://firebasestorage.googleapis.com/.../character.png"
      ],
      "created_at": "2026-10-05T18:22:04.000Z"
    }
  ],
  "next_cursor": "MTc1OTY4ODUyNDAwMDp4N0txMm1WYjlUZlIzbkxwMGFaYw"
}

Notes

  • `duration` is the length of the RENDER, not of the source clip; measure the source (or use `source_trim`) before quoting a run on it.
  • `/v1/videos/gedi/community` is an alias of the same read.

Reference

Trained Models

POST /loras
videos:write

Train a model on your clips

Train a MiniMax H3 adapter (a video LoRA) on your own footage and run it on `video.hailuo_03`. Three things a model can learn: a `style` (a look — grade, lighting, texture — applied to any prompt or start frame; runs image-to-video), a `subject` (a person, product or character rendered consistently from reference media; runs reference-to-video and is the kind Influencer Studio makes), or a `keyframe` (how a shot moves between a first and last frame; runs image-to-video). A fourth kind, `t2v`, learns from prompt-only footage and runs text-to-video; it is API-only. Dataset rules: VIDEO files only (mp4 / mov / webm — no images, no audio-only), either as `clips` / `clip_urls` (up to 40, each ≤200 MB, 1.5 GB total) or as a pre-built `dataset_zip_url` (a .zip of videos). At least 10 clips is recommended — fewer trains, but generalises poorly. A caption per clip is optional and becomes the same-name `.txt` sidecar in the archive (`shot_01.mp4` → `shot_01.txt`); the `trigger_phrase` is prepended to every caption during training and to your prompt at inference. Subject models take up to 4 `reference_urls` sidecars per clip (image, video or audio of the subject); scene splitting is off for subject datasets because it would drop those sidecars. Billing: credits are charged PER STEP, in units of 100 (`POST /loras/estimate-cost` gives the exact figure). `steps` runs 100–6,000 — 6,000 is the provider's billing cap and the API's hard maximum, so a run can never bill more than its quote. Default 2,000. The reservation is refunded if training fails. Training takes tens of minutes: the 202 carries a `lora_training` run — poll `GET /runs/{runId}` (its `output.lora_url` lands on completion) or `GET /loras/{loraId}`, or register a webhook for `run.completed` / `run.failed`. Bill a workspace with `team_id` — this also SHARES the model with that workspace (live members can run it, only you can edit or delete it).

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Body Fields

name string required

Display name (≤120 chars).

kind string required

`style`, `subject`, `keyframe` or `t2v`. Fixes the trainer and the Hailuo 03 endpoint the model runs on.

clips array optional

Training clips as objects: `{ url, caption?, reference_urls? }`. Up to 40. `reference_urls` (≤4 per clip) are subject-model sidecars.

clip_urls array optional

Shorthand for `clips` when you have no captions: an array of video URLs. Send one of `clips` / `clip_urls`, not both.

dataset_zip_url string optional

A pre-built .zip of training videos (with optional same-name .txt captions and, for subject models, `<clip>.ref_1..ref_4.<ext>` sidecars). Skips archive assembly. Cannot be combined with `clips` / `clip_urls`.

trigger_phrase string optional

A short unique token (≤80 chars, e.g. `WARMFILM style`) that activates the model. Prepended to captions during training and to your prompt at inference.

steps integer optional

Training steps, 100–6000 (billed per 100). More steps = stronger fit and higher cost.

Default: 2000

rank integer optional

LoRA rank: 8, 16, 32, 64 or 128. Higher ranks capture more detail and cost nothing extra, but overfit small datasets.

Default: 32

learning_rate number optional

0.000001–1.

Default: 0.0002

train_resolution string optional

`low`, `medium` or `high` — the resolution clips are trained at.

Default: medium

aspect_ratio string optional

Training aspect: 21:9, 16:9, 4:3, 1:1, 3:4, 9:16.

Default: 16:9

frame_count integer optional

Frames per training sample: 22, 39, 56, 73, 90, 107 or 124.

Default: 73

frame_rate integer optional

8–60.

Default: 24

split_scenes boolean optional

Cut clips at scene changes before training. Defaults to true, except for subject models (false — splitting drops the reference sidecars).

strict_dataset boolean optional

Fail the run on any unreadable clip instead of skipping it.

Default: false

resume_from_lora_id string optional

Subject models only: warm-start from one of your completed subject models (same as POST /loras/{loraId}/continue).

influencer_id string optional

Tag a subject model as belonging to one of your influencers.

rights_attested boolean required

Must be `true`: you confirm you hold the rights to every clip in the training set.

team_id string optional

Bill a WORKSPACE credit pool instead of your balance (`GET /v1/teams`). Requires `teams:read`.

quote_token string optional

A signed price quote from the matching estimate endpoint, binding this submit to the price you were shown. May also be sent as the `X-Genfire-Quote` header. The token carries the quoted inputs, so it is only spendable on the request it was issued for: 409 `quote_expired` past its 10-minute life, 409 `quote_mismatch` when the inputs or the price no longer match, 401 `quote_invalid` when the signature does not verify. Every 409 carries a FRESH quote in the problem body so you can resubmit in one step.

curl Example

curl https://api.genfire.ai/v1/loras \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Studio look — warm film",
  "kind": "style",
  "clips": [
    {
      "url": "https://example.com/footage/shot_01.mp4",
      "caption": "slow push-in on a ceramic mug, warm window light"
    },
    {
      "url": "https://example.com/footage/shot_02.mp4",
      "caption": "hand pours coffee, soft shadows"
    }
  ],
  "trigger_phrase": "WARMFILM style",
  "steps": 2000,
  "rights_attested": true
}'

Response Example

{
  "id": "run_lora_123",
  "object": "run",
  "status": "queued",
  "capability": "lora_training",
  "endpoint": "loras.create",
  "model": "video.hailuo_03",
  "request_id": "run_lora_123",
  "input_summary": {
    "name": "Studio look — warm film",
    "kind": "style",
    "trainer": "i2v",
    "clip_count": 14,
    "has_dataset_zip": false,
    "captioned": true,
    "sidecars": false,
    "trigger_phrase": "WARMFILM style",
    "steps": 2000,
    "credits_quoted": 2000
  },
  "output": null,
  "usage": null,
  "error": null,
  "resource_id": "lora_9f2c1a7b",
  "provider_request_id": null,
  "created_at": "2026-09-06T10:00:00.000Z",
  "updated_at": "2026-09-06T10:00:01.000Z",
  "completed_at": null,
  "lora": {
    "id": "lora_9f2c1a7b",
    "object": "lora",
    "name": "Studio look — warm film",
    "kind": "style",
    "status": "training",
    "model": "video.hailuo_03",
    "inference_mode": "i2v",
    "trigger_phrase": "WARMFILM style",
    "steps": 2000,
    "rank": 32,
    "learning_rate": 0.0002,
    "train_resolution": "medium",
    "aspect_ratio": "16:9",
    "frame_count": 73,
    "frame_rate": 24,
    "clip_count": 14,
    "credits_charged": 2000,
    "credits_refunded": null,
    "resume_from_lora_id": null,
    "influencer_id": null,
    "team_id": null,
    "cover_url": null,
    "sample_video_url": null,
    "error": null,
    "created_at": "2026-09-06T10:00:00.000Z",
    "updated_at": "2026-09-06T10:00:01.000Z",
    "completed_at": null
  }
}

Request Example Body

{
  "name": "Studio look — warm film",
  "kind": "style",
  "clips": [
    {
      "url": "https://example.com/footage/shot_01.mp4",
      "caption": "slow push-in on a ceramic mug, warm window light"
    },
    {
      "url": "https://example.com/footage/shot_02.mp4",
      "caption": "hand pours coffee, soft shadows"
    }
  ],
  "trigger_phrase": "WARMFILM style",
  "steps": 2000,
  "rights_attested": true
}

Notes

  • Pick the kind by what you want back: a LOOK → `style`; the SAME person/product across new shots → `subject`; a specific CAMERA MOVE or transition between two frames → `keyframe`.
  • Use it: `POST /videos/generations` with `model: "video.hailuo_03"` and `loras: [{ id: "<lora id>", scale: 1 }]` once `status` is `completed`. Up to 3 models of one kind per clip.
  • Pricing per 100 steps by kind (`GET /models/pricing` keys `minimax_h3_lora_train_*`): t2v is the cheapest, style and keyframe cost twice that, subject three times. Inference bills at the Hailuo 03 LoRA per-second rate.
  • Errors: 400 `invalid_kind` / `invalid_clips` / `too_many_clips` / `invalid_steps` / `rights_not_attested` / `lora_limit` (50 models per account), 402 on insufficient credits, 502 `trainer_submit_failed` (the run is marked failed and the reservation refunded).
POST /loras/estimate-cost
models:read

Estimate training cost

The EXACT credits a training run of `kind` at `steps` will charge — the same quote the training route reserves against. Read-only; no idempotency key.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

kind string required

`style`, `subject`, `keyframe` or `t2v`.

steps integer optional

100–6000.

Default: 2000

curl Example

curl https://api.genfire.ai/v1/loras/estimate-cost \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "kind": "subject",
  "steps": 3000
}'

Response Example

{
  "object": "cost_estimate",
  "model": "video.hailuo_03",
  "capability": "lora_training",
  "kind": "subject",
  "credits": 4500,
  "unit": "total",
  "breakdown": {
    "operation_key": "minimax_h3_lora_train_ref2va",
    "steps": 3000,
    "step_units": 30,
    "credits_per_unit": 150,
    "step_unit": 100
  }
}

Request Example Body

{
  "kind": "subject",
  "steps": 3000
}
GET /loras
models:read

List trained models

Your trained models, newest first. Filter with `status` to find the ones ready to use (`completed`).

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

status query optional

`pending`, `training`, `completed` or `failed`.

limit query optional

1–50 (default 50).

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/loras \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "lora_9f2c1a7b",
      "object": "lora",
      "name": "Studio look — warm film",
      "kind": "style",
      "status": "completed",
      "model": "video.hailuo_03",
      "inference_mode": "i2v",
      "trigger_phrase": "WARMFILM style",
      "steps": 2000,
      "rank": 32,
      "learning_rate": 0.0002,
      "train_resolution": "medium",
      "aspect_ratio": "16:9",
      "frame_count": 73,
      "frame_rate": 24,
      "clip_count": 14,
      "credits_charged": 2000,
      "credits_refunded": null,
      "resume_from_lora_id": null,
      "influencer_id": null,
      "team_id": null,
      "cover_url": null,
      "sample_video_url": null,
      "lora_url": "https://storage.googleapis.com/genfire/video_loras/lora_9f2c1a7b/lora.safetensors",
      "error": null,
      "created_at": "2026-09-06T10:00:00.000Z",
      "updated_at": "2026-09-06T10:41:12.000Z",
      "completed_at": "2026-09-06T10:41:12.000Z"
    }
  ]
}
GET /loras/{loraId}
models:read

Get a trained model

One trained model: status, kind, trigger phrase, training config, credits charged / refunded, error, and — once `completed` — `lora_url`. Polling this is equivalent to polling the training run.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

loraId path required

Trained model id.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/loras/{loraId} \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "id": "lora_9f2c1a7b",
  "object": "lora",
  "name": "Studio look — warm film",
  "kind": "style",
  "status": "completed",
  "model": "video.hailuo_03",
  "inference_mode": "i2v",
  "trigger_phrase": "WARMFILM style",
  "steps": 2000,
  "rank": 32,
  "learning_rate": 0.0002,
  "train_resolution": "medium",
  "aspect_ratio": "16:9",
  "frame_count": 73,
  "frame_rate": 24,
  "clip_count": 14,
  "credits_charged": 2000,
  "credits_refunded": null,
  "resume_from_lora_id": null,
  "influencer_id": null,
  "team_id": null,
  "cover_url": null,
  "sample_video_url": null,
  "lora_url": "https://storage.googleapis.com/genfire/video_loras/lora_9f2c1a7b/lora.safetensors",
  "error": null,
  "created_at": "2026-09-06T10:00:00.000Z",
  "updated_at": "2026-09-06T10:41:12.000Z",
  "completed_at": "2026-09-06T10:41:12.000Z"
}
POST /loras/{loraId}/continue
videos:write

Continue training a subject model

Warm-start a COMPLETED subject model with more steps — optionally on new clips (omit `clips` to reuse the original dataset). Produces a NEW model row (the original is untouched) and a new `lora_training` run, billed like a fresh training run. Subject models only: any other kind is a 400 `resume_not_supported`.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Parameters

loraId path required

The completed subject model to continue from.

Body Fields

steps integer optional

Additional training steps, 100–6000.

Default: 2000

name string optional

Name for the new model. Defaults to `<original name> (continued)`.

clips array optional

New training clips (`{ url, caption?, reference_urls? }`). Omit to reuse the original dataset archive.

clip_urls array optional

Shorthand for `clips` (URLs only).

rights_attested boolean optional

Required when sending new clips.

team_id string optional

Bill a workspace pool. Requires `teams:read`.

curl Example

curl https://api.genfire.ai/v1/loras/{loraId}/continue \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "steps": 1000
}'

Response Example

{
  "id": "run_lora_123",
  "object": "run",
  "status": "queued",
  "capability": "lora_training",
  "endpoint": "loras.continue",
  "model": "video.hailuo_03",
  "request_id": "run_lora_123",
  "input_summary": {
    "name": "Studio look — warm film",
    "kind": "style",
    "trainer": "i2v",
    "clip_count": 14,
    "has_dataset_zip": false,
    "captioned": true,
    "sidecars": false,
    "trigger_phrase": "WARMFILM style",
    "steps": 2000,
    "credits_quoted": 2000
  },
  "output": null,
  "usage": null,
  "error": null,
  "resource_id": "lora_9f2c1a7b",
  "provider_request_id": null,
  "created_at": "2026-09-06T10:00:00.000Z",
  "updated_at": "2026-09-06T10:00:01.000Z",
  "completed_at": null,
  "lora": {
    "id": "lora_9f2c1a7b",
    "object": "lora",
    "name": "Studio look — warm film",
    "kind": "style",
    "status": "training",
    "model": "video.hailuo_03",
    "inference_mode": "i2v",
    "trigger_phrase": "WARMFILM style",
    "steps": 2000,
    "rank": 32,
    "learning_rate": 0.0002,
    "train_resolution": "medium",
    "aspect_ratio": "16:9",
    "frame_count": 73,
    "frame_rate": 24,
    "clip_count": 14,
    "credits_charged": 2000,
    "credits_refunded": null,
    "resume_from_lora_id": null,
    "influencer_id": null,
    "team_id": null,
    "cover_url": null,
    "sample_video_url": null,
    "error": null,
    "created_at": "2026-09-06T10:00:00.000Z",
    "updated_at": "2026-09-06T10:00:01.000Z",
    "completed_at": null
  }
}

Request Example Body

{
  "steps": 1000
}
DELETE /loras/{loraId}
videos:write

Delete a trained model

Deletes a trained model you own. A model that is still training cannot be deleted (409 `lora_training`) — wait for it to finish or fail.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

loraId path required

Trained model id.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/loras/{loraId} \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "id": "lora_9f2c1a7b",
  "object": "lora",
  "deleted": true
}
GET /image-styles
models:read

List trained image styles

Every Flux image LoRA you can generate with: the ones you trained yourself plus the ones shared with you by every team you belong to, de-duplicated and newest first. A `completed` entry's `id` is what goes in a `loras[]` entry on `POST /v1/images/generations`, and its `trigger_word` is the token the prompt has to contain for the style to actually apply. `kind` says what the adapter learned: a `style` is a look, a `subject` is a thing or a person. Free, and metadata only — no run is created.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

status query optional

Filter to one state: `queued`, `training`, `completed` or `failed`. Anything else is 400 `invalid_status`.

limit query optional

Max styles to return, 1-50.

Default: 50

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/image-styles \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "lora_img_9f2c1a7b",
      "object": "image_style",
      "name": "Risograph poster",
      "kind": "style",
      "status": "completed",
      "model": "image.flux_lora",
      "trigger_word": "RISOPOSTER",
      "steps": 1000,
      "image_count": 18,
      "cover_urls": [
        "https://cdn.genfire.ai/styles/riso-1.png"
      ],
      "skill_id": "skill_4d1e",
      "team_id": null,
      "credits_charged": 100,
      "credits_refunded": 0,
      "weights_url": "https://storage.googleapis.com/genfire/loras/lora_img_9f2c1a7b.safetensors",
      "error": null,
      "created_at": "2026-09-01T12:00:00.000Z",
      "completed_at": "2026-09-01T12:26:00.000Z"
    }
  ]
}

Notes

  • The listing merges your own styles with every team-shared one, so an `id` here may belong to a team rather than to you — `team_id` says which.
  • `weights_url` appears only on a `completed` style, and `error` only on a failed one.
  • Read one by id with `GET /v1/image-styles/{styleId}`; train a new one with `POST /v1/image-styles` or, from a style skill, `POST /v1/style-skills/{skillId}/train`.
  • Each style carries `base_model` — `flux` or `z_image_turbo` — and weights are NOT portable between them. `POST /v1/image-styles` takes `base_model` (default `flux`); `z_image_turbo` is cheaper to train (quoted from a 500-step floor — price it with `POST /v1/image-styles/estimate-cost` and the same `base_model`). A `flux` style runs on `image.flux_lora`; a `z_image_turbo` style is an OPTIONAL `loras[]` entry on `image.z_image_turbo`, `image.z_image_controlnet` and `image.z_image_tiling`. The style's `model` field names the alias to use; the wrong family is 400 `lora_base_mismatch`, before any credits move.
POST /style-skills/{skillId}/train
images:write

Train a style skill into an image style

Turn a style SKILL — a saved set of reference images — into a trained Flux LoRA you can generate with. This is the shortcut past `POST /v1/image-styles`: the skill already holds the images, so the only decision left is how long to train. `steps` is the whole cost lever and the whole quality lever. Credits are charged per 100-step unit, rounded up, so 1000 steps costs ten units; more steps learn the look harder and overfit it sooner. Price it with `POST /v1/image-styles/estimate-cost` before committing. BILLED UP FRONT and refunded only if the PROVIDER fails — never because the result disappoints. Async: returns the style in `queued`/`training`; poll `GET /v1/image-styles/{styleId}` until `completed`, then use its `id` and `trigger_word` on `POST /v1/images/generations`.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Parameters

skillId path required

A style skill id from `GET /v1/style-skills`. A skill that is not a style skill is 404 `skill_not_found`.

Body Fields

steps number optional

Training steps, 100-4000. Billed per 100-step unit, rounded up. Out of range is 400 `invalid_steps`.

Default: 1000

team_id string optional

Bill a workspace (team) credit pool instead of the personal balance. Requires the `teams:read` scope and a spending role.

curl Example

curl https://api.genfire.ai/v1/style-skills/{skillId}/train \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "steps": 1200
}'

Response Example

{
  "id": "lora_img_4d1e8a2b",
  "object": "image_style",
  "name": "Risograph poster",
  "kind": "style",
  "status": "queued",
  "model": "image.flux_lora",
  "trigger_word": "RISOPOSTER",
  "steps": 1200,
  "image_count": 18,
  "cover_urls": [],
  "skill_id": "skill_4d1e",
  "team_id": null,
  "credits_charged": 1200,
  "credits_refunded": 0,
  "error": null,
  "created_at": "2026-09-12T10:00:00.000Z",
  "completed_at": null
}

Request Example Body

{
  "steps": 1200
}

Notes

  • Returns 202 with the style record — there is no `api_runs` row for this one, so poll `GET /v1/image-styles/{styleId}` rather than `GET /v1/runs/{id}`.
  • A skill already training is 409 `lora_training`, one already trained is 409 `lora_exists`, and a skill with fewer than 5 reference images is 400 `no_images`.
  • The `Idempotency-Key` also keys the credit reservation, so a replayed request re-uses it instead of training (and charging) twice.
  • `project_id` is NOT accepted — training produces a style, not media a project can hold, so naming one is 400 `project_filing_unsupported`.

Reference

Workflows

GET /workflows
workflows:read

List executable workflows

Returns the workflow catalog. Use this when you want a higher-level outcome rather than raw media generation.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/workflows \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "hook_pack",
      "object": "workflow",
      "name": "Hook Pack",
      "description": "Generate three monetizable UGC-style hooks plus a supporting body and CTA.",
      "status": "available",
      "input_schema": {
        "type": "object",
        "required": [
          "prompt"
        ],
        "properties": {
          "prompt": {
            "type": "string"
          },
          "audience": {
            "type": "string"
          },
          "tone": {
            "type": "string",
            "enum": [
              "casual",
              "professional",
              "humorous",
              "educational",
              "persuasive",
              "emotional"
            ]
          },
          "length": {
            "type": "string",
            "enum": [
              "short",
              "medium",
              "long"
            ]
          },
          "target_duration": {
            "type": "number"
          },
          "keywords": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "additional_requirements": {
            "type": "string"
          }
        }
      },
      "output_schema": {
        "type": "object",
        "properties": {
          "hooks": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "body": {
            "type": "string"
          },
          "call_to_action": {
            "type": "string"
          },
          "full_script": {
            "type": "string"
          }
        }
      }
    },
    {
      "id": "ugc_ad",
      "object": "workflow",
      "name": "UGC Ad Script",
      "description": "Generate an outcome-oriented UGC ad script with hooks, body, CTA, and a full spoken script.",
      "status": "available",
      "input_schema": {
        "type": "object",
        "required": [
          "prompt"
        ],
        "properties": {
          "prompt": {
            "type": "string"
          },
          "audience": {
            "type": "string"
          },
          "tone": {
            "type": "string",
            "enum": [
              "casual",
              "professional",
              "humorous",
              "educational",
              "persuasive",
              "emotional"
            ]
          },
          "length": {
            "type": "string",
            "enum": [
              "short",
              "medium",
              "long"
            ]
          },
          "target_duration": {
            "type": "number"
          },
          "keywords": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "additional_requirements": {
            "type": "string"
          }
        }
      },
      "output_schema": {
        "type": "object",
        "properties": {
          "hooks": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "body": {
            "type": "string"
          },
          "call_to_action": {
            "type": "string"
          },
          "full_script": {
            "type": "string"
          }
        }
      }
    },
    {
      "id": "ugc_ad_video",
      "object": "workflow",
      "name": "UGC Ad Video",
      "description": "One run → a finished UGC video ad: researches and writes the script (brand/product aware), generates the talking-avatar product shot on Seedance 2.5 (with optional avatar template voice), and burns styled captions. Async — poll the returned run for the video URL. Cost = the component script workflow + the Seedance 2.5 generation (captions are free).",
      "status": "available",
      "input_schema": {
        "type": "object",
        "required": [
          "prompt"
        ],
        "properties": {
          "prompt": {
            "type": "string",
            "description": "The product/offer brief the ad should sell. Required unless a finished `script` is supplied."
          },
          "script": {
            "type": "string",
            "description": "Skip script generation and use this exact spoken script."
          },
          "brand_id": {
            "type": "string",
            "description": "Ground the ad on a stored brand: its featured product photo fills the product slot and its voice/palette/words-to-avoid condition the script. The same brand_id contract as image/video generation and faceless reels."
          },
          "product_url": {
            "type": "string",
            "format": "uri",
            "description": "Product page URL — switches script generation to the product-aware creative_pack research flow."
          },
          "product_image_url": {
            "type": "string",
            "format": "uri",
            "description": "Product photo the creator presents on camera (reference image)."
          },
          "avatar_template_id": {
            "type": "string",
            "description": "Avatar from the Genfire template library — brings the face AND (by default) its voice sample."
          },
          "avatar_image_url": {
            "type": "string",
            "format": "uri",
            "description": "Custom avatar/creator face image (alternative to avatar_template_id)."
          },
          "use_avatar_voice": {
            "type": "boolean",
            "default": true,
            "description": "Condition the speech on the avatar template's voice sample when one exists."
          },
          "audience": {
            "type": "string"
          },
          "tone": {
            "type": "string",
            "enum": [
              "casual",
              "professional",
              "humorous",
              "educational",
              "persuasive",
              "emotional"
            ]
          },
          "scene": {
            "type": "string",
            "description": "Optional scene direction (e.g. \"in a car, golden hour\")."
          },
          "duration_seconds": {
            "type": "number",
            "minimum": 5,
            "maximum": 60,
            "default": 12,
            "description": "Up to 60s: ads longer than 30s chain multiple voice/face-conditioned takes with jump cuts (the native UGC edit)."
          },
          "aspect_ratio": {
            "type": "string",
            "enum": [
              "9:16",
              "16:9",
              "1:1",
              "3:4",
              "4:3",
              "21:9"
            ],
            "default": "9:16"
          },
          "resolution": {
            "type": "string",
            "enum": [
              "480p",
              "720p",
              "1080p"
            ],
            "default": "720p",
            "description": "Legacy 768p / 2k are still accepted and map to 720p / 1080p."
          },
          "captions": {
            "type": "boolean",
            "default": true
          },
          "caption_preset_id": {
            "type": "string",
            "default": "hormozi"
          },
          "caption_position": {
            "type": "string",
            "enum": [
              "top",
              "middle",
              "bottom"
            ],
            "default": "bottom"
          },
          "caption_words_per_line": {
            "type": "number",
            "minimum": 1,
            "maximum": 6
          },
          "broll": {
            "type": "string",
            "enum": [
              "none",
              "auto",
              "stock",
              "stills",
              "video"
            ],
            "default": "auto",
            "description": "Multi-scene cutaways while the avatar voice continues. auto (default) = one product-fidelity still + FREE stock clips timed to spoken keywords; stock = all free stock footage; stills = generated images; video = Seedance 2.5 clips (premium)."
          },
          "broll_count": {
            "type": "number",
            "minimum": 1,
            "maximum": 3,
            "default": 2
          },
          "reference_ad_research_id": {
            "type": "string",
            "description": "A stored competitor-ad analysis (from POST /v1/ads/analyze or the agent's analyze_competitor_ad) — the script follows that proven format for YOUR product; assets and wording are never copied."
          }
        }
      },
      "output_schema": {
        "type": "object",
        "properties": {
          "video_url": {
            "type": "string"
          },
          "captioned": {
            "type": "boolean"
          },
          "script": {
            "type": "string"
          },
          "hooks": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "source_video_id": {
            "type": "string"
          },
          "duration_seconds": {
            "type": "number"
          },
          "aspect_ratio": {
            "type": "string"
          }
        }
      }
    },
    {
      "id": "ad_remix",
      "object": "workflow",
      "name": "Ad Remix",
      "description": "One winning ad → variants with the nouns swapped, the cut and pacing kept. Engine: Gemini Omni Flash 1.1, two routes chosen per variant. EDIT (every change is words): the footage itself is edited, so the setting, framing, motion and product are kept by construction — a ≤10s source goes whole, a longer one is cut on its own hard cuts into ≤10s segments and each is edited; variants that ACCUMULATE (each one's changes extend the previous one's, in order) are CHAINED — each edits the previous variant's output with only what it adds, so a new creator carries through outfit → location → language. REFERENCE (a change shows an image): the source is split on its own hard cuts into ≤9s segments (each fed three ≤3s slices of itself as references). MIXED (words AND a picture in the same variant — \"make her Persian and put her in this hoodie\"): the server SPLITS it into an implicit two-step chain and still returns ONE variant — an intermediate leg (id `{id}.edit`, `intermediate: true`) renders the words-only changes on the edit route, then the deliverable renders the image-backed ones on the reference route with its slices cut from THAT LEG'S OUTPUT (`chain_from`), so a words-only person swap actually lands instead of coming back as the original person. Both legs are billed; only the deliverable appears in output.variants[], the leg appears under output.intermediates[]. Never restructure a request to work around routing — put every change on one variant and let the server split it. A creator change that carries a face image is DESCRIBED in words by the server and the picture is never sent with footage (the engine refuses a face next to footage) — the new person is a look-alike of the description; picker cards (creator portrait, outfit flat lay, empty location) are generated AFTER each video from its own frame so the card is what rendered. Every (variant × segment) is one generation, cards ~1 credit each, and the segments are concatenated on the planned boundaries with the sound the model rendered — it renders speech, ambience and music with the picture, mouths in sync, so a creator swap or a language change ({ what: \"language\", description: \"Spanish\" }) comes back dubbed with no lipsync step. The original track (or a supplied one) can be laid back on instead. Each output is cut-diffed against the source and returns a fidelity score. Async — poll the returned run; output.variants[] carries a video_url per variant. Cost = segments × variants × per-second Omni pricing at the chosen resolution — quote it first with POST /v1/ads/remix (dry run).",
      "status": "available",
      "input_schema": {
        "type": "object",
        "required": [
          "source_video_url"
        ],
        "properties": {
          "source_video_url": {
            "type": "string",
            "format": "uri",
            "description": "The winning ad, as a public https URL (a Genfire run output URL works). Clean sources only — burned-in captions come back as garbled text."
          },
          "product_image_url": {
            "type": "string",
            "format": "uri",
            "description": "The product, PINNED: an Element image or product photo that rides every REFERENCE generation as a fixed object reference (\"the product, exactly as shown\"). Without it the model re-draws the product from the reference slices and it drifts between variants. It is NOT sent on an edit-route call — that endpoint has no image pool, so there the product is held from the footage by the keep-list (\"exactly as it is in the video\") and the plan says so in developer_notes. A `product` CHANGE pointing at this same image is folded back into the pin rather than honoured as a change, so repeating the product photo can never push a variant onto the reference route on its own. The pin steps aside entirely for a variant that changes the product to a DIFFERENT image."
          },
          "changes": {
            "type": "array",
            "minItems": 1,
            "maxItems": 10,
            "description": "ONE variant: what to change. Use this OR `variants`.",
            "items": {
              "type": "object",
              "required": [
                "what"
              ],
              "properties": {
                "what": {
                  "type": "string",
                  "description": "\"creator\", \"outfit\", \"location\", \"product\" get tuned phrasing; anything else (\"the drink she is holding\", \"time of day\") is honoured generically."
                },
                "image_url": {
                  "type": "string",
                  "format": "uri",
                  "description": "Reference image showing the replacement (an Element, a product photo, an outfit or location still). For a CREATOR change the face is described in words server-side and the picture is never sent with footage — the engine refuses that — so the result is a look-alike, not that exact face; the creator card in the output is made from the render."
                },
                "description": {
                  "type": "string",
                  "description": "Plain-English detail. Required when there is no image_url or preset."
                },
                "preset": {
                  "type": "string",
                  "description": "A named preset with the wording already written (no image slot used; `what` may be omitted). look: anime, retro-collage, bold-urban, green-contrast, warm-glow, neon-cyberpunk, claymation, comic-pop, doodle-notes, vhs-retro, paper-craft, watercolor, noir-film, hud-tracking · location: marble-kitchen, gym-floor, car-interior, golden-hour-street, cafe-terrace, beach-boardwalk, city-rooftop-dusk, bedroom-morning, home-office, city-park, airplane-wing, volcano-rim, doll-size, moving-car-roof · time: golden-hour, blue-hour, midday, overcast, night, indoor-evening · weather: light-rain, snow, fog, heat-haze · wardrobe: athleisure, streetwear, business-casual, evening-wear, cozy-knit, workwear · camera: handheld-phone, tripod-locked, slow-push-in, wider-lens · language: en, es, de, fr, it, pt-br, ja, ko, zh, hi, ar. The dry run lists them under `presets`."
                }
              }
            }
          },
          "variants": {
            "type": "array",
            "minItems": 1,
            "maxItems": 12,
            "description": "SEVERAL variants, each a full remix of the whole ad. Use this OR `changes`.",
            "items": {
              "type": "object",
              "required": [
                "changes"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "changes": {
                  "type": "array",
                  "minItems": 1,
                  "maxItems": 10,
                  "items": {
                    "type": "object",
                    "required": [
                      "what"
                    ],
                    "properties": {
                      "what": {
                        "type": "string",
                        "description": "\"creator\", \"outfit\", \"location\", \"product\" get tuned phrasing; anything else (\"the drink she is holding\", \"time of day\") is honoured generically."
                      },
                      "image_url": {
                        "type": "string",
                        "format": "uri",
                        "description": "Reference image showing the replacement (an Element, a product photo, an outfit or location still). For a CREATOR change the face is described in words server-side and the picture is never sent with footage — the engine refuses that — so the result is a look-alike, not that exact face; the creator card in the output is made from the render."
                      },
                      "description": {
                        "type": "string",
                        "description": "Plain-English detail. Required when there is no image_url or preset."
                      },
                      "preset": {
                        "type": "string",
                        "description": "A named preset with the wording already written (no image slot used; `what` may be omitted). look: anime, retro-collage, bold-urban, green-contrast, warm-glow, neon-cyberpunk, claymation, comic-pop, doodle-notes, vhs-retro, paper-craft, watercolor, noir-film, hud-tracking · location: marble-kitchen, gym-floor, car-interior, golden-hour-street, cafe-terrace, beach-boardwalk, city-rooftop-dusk, bedroom-morning, home-office, city-park, airplane-wing, volcano-rim, doll-size, moving-car-roof · time: golden-hour, blue-hour, midday, overcast, night, indoor-evening · weather: light-rain, snow, fog, heat-haze · wardrobe: athleisure, streetwear, business-casual, evening-wear, cozy-knit, workwear · camera: handheld-phone, tripod-locked, slow-push-in, wider-lens · language: en, es, de, fr, it, pt-br, ja, ko, zh, hi, ar. The dry run lists them under `presets`."
                      }
                    }
                  }
                },
                "direction": {
                  "type": "string",
                  "description": "Free-text direction for this variant, appended verbatim."
                },
                "audio": {
                  "anyOf": [
                    {
                      "type": "string",
                      "enum": [
                        "generated",
                        "original",
                        "none"
                      ]
                    },
                    {
                      "type": "string",
                      "format": "uri"
                    },
                    {
                      "type": "object",
                      "required": [
                        "mode"
                      ],
                      "properties": {
                        "mode": {
                          "type": "string",
                          "enum": [
                            "generated",
                            "original",
                            "replace",
                            "none"
                          ]
                        },
                        "url": {
                          "type": "string",
                          "format": "uri"
                        }
                      }
                    }
                  ],
                  "description": "\"generated\" (default — the model's own sound, in sync with its mouths), \"original\" (the source track laid back on), \"none\", an audio URL, or { mode: generated|original|replace|none, url }."
                }
              }
            }
          },
          "audio": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "generated",
                  "original",
                  "none"
                ]
              },
              {
                "type": "string",
                "format": "uri"
              },
              {
                "type": "object",
                "required": [
                  "mode"
                ],
                "properties": {
                  "mode": {
                    "type": "string",
                    "enum": [
                      "generated",
                      "original",
                      "replace",
                      "none"
                    ]
                  },
                  "url": {
                    "type": "string",
                    "format": "uri"
                  }
                }
              }
            ],
            "description": "Run-level soundtrack default: \"generated\" (the sound the model rendered with the picture — default), \"original\" (the source track laid back on), \"none\", an http(s) audio URL, or { mode, url }. A variant's own `audio` wins."
          },
          "aspect_ratio": {
            "type": "string",
            "enum": [
              "9:16",
              "16:9"
            ],
            "default": "9:16",
            "description": "Match the source."
          },
          "resolution": {
            "type": "string",
            "enum": [
              "360p",
              "720p",
              "1080p",
              "4k"
            ],
            "default": "1080p",
            "description": "720p for drafts — cost scales ~10x across the ladder."
          },
          "cards": {
            "type": "boolean",
            "default": true,
            "description": "Make picker cards from each variant's output — a creator portrait, an outfit flat lay, an empty location — for the changes it introduces (~1 credit each). false to skip."
          }
        }
      },
      "output_schema": {
        "type": "object",
        "properties": {
          "engine": {
            "type": "string"
          },
          "route": {
            "type": "string",
            "enum": [
              "edit",
              "reference",
              "mixed"
            ]
          },
          "segments": {
            "type": "number"
          },
          "generation_count": {
            "type": "number"
          },
          "cards": {
            "type": "number",
            "description": "How many picker cards were planned."
          },
          "intermediates": {
            "type": "array",
            "description": "Debugging only: the words-only legs the server inserted for variants that mixed words with pictures. Each was billed and rendered; none is a deliverable — they never appear in variants[] and can never be the run's video_url.",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "completed",
                    "failed"
                  ]
                },
                "intermediate": {
                  "type": "boolean"
                },
                "video_url": {
                  "type": "string"
                },
                "chain_from": {
                  "type": "string"
                },
                "error": {
                  "type": "string"
                }
              }
            }
          },
          "variants": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "completed",
                    "failed"
                  ]
                },
                "video_url": {
                  "type": "string"
                },
                "duration_seconds": {
                  "type": "number"
                },
                "qa": {
                  "type": "object",
                  "description": "Cut-diff against the source: fidelity 0-1, matched/missing/extra cuts, mean drift."
                },
                "audio_track": {
                  "type": "string",
                  "enum": [
                    "generated",
                    "original",
                    "replace",
                    "none"
                  ],
                  "description": "Which soundtrack shipped on this variant."
                },
                "thumbnail_url": {
                  "type": "string"
                },
                "cards": {
                  "type": "object",
                  "description": "Picker cards made from THIS output: { creator?, outfit?, location? } → image URL. The card is what rendered."
                },
                "chain_from": {
                  "type": "string",
                  "description": "The variant this one was rendered on top of — an earlier variant it accumulates on, or the intermediate leg the server inserted when this variant mixed words-only changes with image-backed ones."
                },
                "creator_reference_url": {
                  "type": "string",
                  "description": "The face the caller picked for the creator change — described, never sent to the engine."
                },
                "library_video_id": {
                  "type": "string"
                },
                "error": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    {
      "id": "creative_pack",
      "object": "workflow",
      "name": "Creative Pack",
      "description": "Build a product-aware creative pack with analysis, angles, hooks, and a monetizable script.",
      "status": "available",
      "input_schema": {
        "type": "object",
        "properties": {
          "prompt": {
            "type": "string"
          },
          "product_url": {
            "type": "string",
            "format": "uri"
          },
          "product_name": {
            "type": "string"
          },
          "product_description": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "audience": {
            "type": "string"
          },
          "tone": {
            "type": "string",
            "enum": [
              "casual",
              "professional",
              "humorous",
              "educational",
              "persuasive",
              "emotional"
            ]
          },
          "length": {
            "type": "string",
            "enum": [
              "short",
              "medium",
              "long"
            ]
          },
          "image_urls": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            }
          },
          "additional_requirements": {
            "type": "string"
          }
        }
      },
      "output_schema": {
        "type": "object",
        "properties": {
          "product": {
            "type": "object"
          },
          "analysis": {
            "type": "object"
          },
          "hooks": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "body": {
            "type": "string"
          },
          "call_to_action": {
            "type": "string"
          },
          "full_script": {
            "type": "string"
          }
        }
      }
    },
    {
      "id": "product_ad_variants",
      "object": "workflow",
      "name": "Product Ad Variants",
      "description": "Generate multiple UGC-style ad script variants for the same product or offer.",
      "status": "available",
      "input_schema": {
        "type": "object",
        "properties": {
          "prompt": {
            "type": "string"
          },
          "product_name": {
            "type": "string"
          },
          "product_description": {
            "type": "string"
          },
          "audience": {
            "type": "string"
          },
          "tone": {
            "type": "string",
            "enum": [
              "casual",
              "professional",
              "humorous",
              "educational",
              "persuasive",
              "emotional"
            ]
          },
          "length": {
            "type": "string",
            "enum": [
              "short",
              "medium",
              "long"
            ]
          },
          "variant_count": {
            "type": "number",
            "minimum": 1,
            "maximum": 5
          },
          "additional_requirements": {
            "type": "string"
          }
        }
      },
      "output_schema": {
        "type": "object",
        "properties": {
          "variant_count": {
            "type": "number"
          },
          "variants": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        }
      }
    }
  ]
}
GET /workflows/{workflowKey}
workflows:read

Get one workflow definition

Returns the input and output schema for a specific workflow.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

workflowKey path required

A workflow id such as `hook_pack` or `creative_pack`.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/workflows/{workflowKey} \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "id": "hook_pack",
  "object": "workflow",
  "name": "Hook Pack",
  "description": "Generate three monetizable UGC-style hooks plus a supporting body and CTA.",
  "status": "available",
  "input_schema": {
    "type": "object",
    "required": [
      "prompt"
    ],
    "properties": {
      "prompt": {
        "type": "string"
      },
      "audience": {
        "type": "string"
      },
      "tone": {
        "type": "string",
        "enum": [
          "casual",
          "professional",
          "humorous",
          "educational",
          "persuasive",
          "emotional"
        ]
      },
      "length": {
        "type": "string",
        "enum": [
          "short",
          "medium",
          "long"
        ]
      },
      "target_duration": {
        "type": "number"
      },
      "keywords": {
        "type": "array",
        "items": {
          "type": "string"
        }
      },
      "additional_requirements": {
        "type": "string"
      }
    }
  },
  "output_schema": {
    "type": "object",
    "properties": {
      "hooks": {
        "type": "array",
        "items": {
          "type": "string"
        }
      },
      "body": {
        "type": "string"
      },
      "call_to_action": {
        "type": "string"
      },
      "full_script": {
        "type": "string"
      }
    }
  }
}
POST /workflows/{workflowKey}/runs
workflows:write

Execute a workflow

Creates a run for the requested workflow. The request body depends on the workflow you choose.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Parameters

workflowKey path required

A workflow id such as `hook_pack`, `ugc_ad`, or `creative_pack`.

Body Fields

workflow-specific fields object required

Use the schema from `GET /workflows/{workflowKey}` or the workflow catalog below.

team_id string optional

Bill this run to a WORKSPACE (team) credit pool instead of your balance (`GET /v1/teams`). Membership and a spending role are asserted before the run is created — 404 `invalid_team_id` when you are not a member, 403 `team_role_cannot_spend` when your role cannot spend. Requires the `teams:read` scope. A route that cannot bill a pool yet returns 400 `team_billing_unsupported` rather than quietly charging you. The run echoes it back as `team_id`.

curl Example

curl https://api.genfire.ai/v1/workflows/{workflowKey}/runs \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "prompt": "Portable blender for busy professionals",
  "audience": "Remote workers who skip breakfast",
  "tone": "persuasive",
  "length": "short",
  "target_duration": 20,
  "keywords": [
    "protein shake",
    "commute",
    "healthy"
  ],
  "additional_requirements": "Lead with convenience and save time."
}'

Response Example

{
  "id": "run_workflow_123",
  "object": "run",
  "status": "completed",
  "capability": "workflow.hook_pack",
  "endpoint": "workflows.hook_pack.run",
  "model": null,
  "request_id": "run_img_123",
  "input_summary": {
    "workflow": "hook_pack",
    "prompt": "Portable blender for busy professionals",
    "audience": "Remote workers who skip breakfast",
    "tone": "persuasive",
    "length": "short",
    "target_duration": 20,
    "keywords": [
      "protein shake",
      "commute",
      "healthy"
    ],
    "additional_requirements": "Lead with convenience and save time."
  },
  "output": {
    "workflow": "hook_pack",
    "topic": "Portable blender for busy professionals",
    "hooks": [
      "If breakfast keeps losing to your calendar, this fixes it.",
      "I stopped skipping protein the day I threw this in my bag.",
      "This is how I make a full shake before my first meeting."
    ],
    "body": "Portable, fast, and easy to clean for daily use.",
    "call_to_action": "Try it before your next busy week.",
    "full_script": "If breakfast keeps losing to your calendar...",
    "word_count": 96,
    "estimated_duration": 22
  },
  "usage": {
    "credits": 8
  },
  "error": null,
  "resource_id": null,
  "provider_request_id": null,
  "team_id": null,
  "project_id": null,
  "created_at": "2026-03-25T12:00:00.000Z",
  "updated_at": "2026-03-25T12:00:02.000Z",
  "completed_at": "2026-03-25T12:00:02.000Z"
}

Request Example Body

{
  "prompt": "Portable blender for busy professionals",
  "audience": "Remote workers who skip breakfast",
  "tone": "persuasive",
  "length": "short",
  "target_duration": 20,
  "keywords": [
    "protein shake",
    "commute",
    "healthy"
  ],
  "additional_requirements": "Lead with convenience and save time."
}

Notes

  • The workflow catalog below includes example payloads for every supported workflow.

Reference

User Workflows

GET /user-workflows
workflows:read

List your editor workflows

Lists the node-graph workflows the authenticated account owns or can access through team sharing — the same workflows shown in the dashboard workflow editor. Use the returned `id` to trigger runs via `POST /user-workflows/{workflowId}/runs`.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/user-workflows \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "wf_abc123",
      "object": "user_workflow",
      "title": "Product shot pipeline",
      "nodeCount": 6,
      "updatedAt": "2026-07-01T12:00:00.000Z",
      "teamId": null
    }
  ]
}
POST /user-workflows/{workflowId}/runs
workflows:write

Run one of your editor workflows

Starts a run of a user-created workflow graph — the same execution path as the editor’s Run button. Execution is asynchronous: this returns `202 Accepted` immediately with a run id; poll `GET /user-workflows/{workflowId}/runs/{runId}` for progress and per-node outputs. Credits are charged to the authenticated account per node as the graph executes (runner pays).

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

workflowId path required

A workflow id from `GET /user-workflows`.

Body Fields

pageId string optional

Which page of the workflow to run. Defaults to the first page.

selectedNodeIds array optional

Node ids to run — their upstream dependencies run automatically. Defaults to the editor behavior: the page’s Export nodes if it has any, otherwise every generation leaf node (image/video/audio/text nodes with no outgoing edges).

paramOverrides object optional

Per-run parameter overrides shaped as `{ nodeId: { paramKey: value } }`. Values must be primitives (string, number, boolean, or null) and the object must serialize to at most 50KB. Overrides apply to this run only — the stored workflow is never modified.

quote_token string optional

A signed price quote from the matching estimate endpoint, binding this submit to the price you were shown. May also be sent as the `X-Genfire-Quote` header. The token carries the quoted inputs, so it is only spendable on the request it was issued for: 409 `quote_expired` past its 10-minute life, 409 `quote_mismatch` when the inputs or the price no longer match, 401 `quote_invalid` when the signature does not verify. Every 409 carries a FRESH quote in the problem body so you can resubmit in one step.

curl Example

curl https://api.genfire.ai/v1/user-workflows/{workflowId}/runs \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "pageId": "page_1",
  "paramOverrides": {
    "node_prompt_1": {
      "text": "A red sneaker on a marble table"
    }
  }
}'

Response Example

{
  "runId": "wfrun_abc123",
  "totalCostCredits": 42,
  "pageId": "page_1",
  "selectedNodeIds": [
    "node_export_1"
  ]
}

Request Example Body

{
  "pageId": "page_1",
  "paramOverrides": {
    "node_prompt_1": {
      "text": "A red sneaker on a marble table"
    }
  }
}

Notes

  • Requires owner or editor access to the workflow; team viewers receive a 403.
  • `totalCostCredits` is the up-front estimate for the nodes that will execute; per-node charges settle against your credit balance as the run progresses.
  • Snake_case body keys (`page_id`, `selected_node_ids`, `param_overrides`) are accepted as aliases.
POST /user-workflows/{workflowId}/estimate
workflows:write

Price a run before starting it

Returns the credit cost of running the workflow with the given selection and overrides, without starting a run and without charging anything. The number is produced by the same estimator `POST /user-workflows/{workflowId}/runs` uses to set `totalCostCredits`, so a quote and the run that follows it cannot disagree.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

workflowId path required

A workflow id from `GET /user-workflows`.

Body Fields

page_id string optional

Which page to price. Defaults to the first page.

selected_node_ids array optional

Node ids to price — their upstream dependencies are included automatically. Defaults to the same selection a run would use.

param_overrides object optional

Per-run parameter overrides shaped as `{ nodeId: { paramKey: value } }`. Priced exactly as the run would apply them — cost-relevant params (durations, resolutions, models) change the quote.

curl Example

curl https://api.genfire.ai/v1/user-workflows/{workflowId}/estimate \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "param_overrides": {
    "node_video_1": {
      "duration": 10
    }
  }
}'

Response Example

{
  "object": "cost_estimate",
  "credits": 42,
  "unit": "credits",
  "breakdown": [
    {
      "node_id": "node_prompt_1",
      "kind": "prompt",
      "credits": 0,
      "cached": true
    },
    {
      "node_id": "node_image_1",
      "kind": "image",
      "credits": 8,
      "cached": false
    },
    {
      "node_id": "node_video_1",
      "kind": "video",
      "credits": 34,
      "cached": false
    },
    {
      "node_id": "node_export_1",
      "kind": "export",
      "credits": 0,
      "cached": false
    }
  ],
  "workflow_rev": 17,
  "page_id": "page_1",
  "selected_node_ids": [
    "node_export_1"
  ],
  "quote_id": "quote_9f1c2ab34d5e6f70",
  "expires_at": "2026-09-12T18:24:00.000Z",
  "quote_token": "eyJxIjoicXVvdGVfOWYxYzJhYjM0ZDVlNmY3MCIsLi4ufQ.f3Kb2p9wQm1sZ0yVxT4uR7NcH6L8dJeA5gXqW0oP1sY"
}

Request Example Body

{
  "param_overrides": {
    "node_video_1": {
      "duration": 10
    }
  }
}

Notes

  • Free. No run is created, no credits are reserved, and nothing is charged.
  • `cached: true` marks an upstream node that already holds a persisted output and will be reused rather than re-run — it contributes 0 to the total. It is listed rather than omitted so the breakdown still describes the whole chain.
  • `workflow_rev` is the graph revision the quote was computed against; a run started after the workflow is edited may cost something different.
  • Snake_case and camelCase body keys are both accepted, as on the run endpoint.
GET /user-workflows/{workflowId}/runs/{runId}
workflows:read

Get the status and outputs of a workflow run

Returns run-level status plus one entry per executed node with its output media URL or text, and an `output` object splitting those node outputs into deliverables and intermediates. Poll until `status` is `completed`, `failed`, or `cancelled`.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

workflowId path required

A workflow id from `GET /user-workflows`.

runId path required

The run id returned by `POST /user-workflows/{workflowId}/runs`.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/user-workflows/{workflowId}/runs/{runId} \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "runId": "wfrun_abc123",
  "workflowId": "wf_abc123",
  "pageId": "page_1",
  "status": "completed",
  "totalCostCredits": 42,
  "workflowRev": 17,
  "startedAt": "2026-07-01T12:00:01.000Z",
  "completedAt": "2026-07-01T12:02:40.000Z",
  "error": null,
  "nodes": [
    {
      "nodeId": "node_image_1",
      "status": "completed",
      "output": {
        "type": "image",
        "url": "https://firebasestorage.googleapis.com/.../shot.png"
      }
    },
    {
      "nodeId": "node_export_1",
      "status": "completed",
      "output": {
        "type": "file",
        "url": "https://firebasestorage.googleapis.com/.../export.png"
      }
    }
  ],
  "output": {
    "deliverables": [
      {
        "node_id": "node_export_1",
        "kind": "export",
        "output": {
          "type": "file",
          "url": "https://firebasestorage.googleapis.com/.../export.png"
        }
      }
    ],
    "intermediates": [
      {
        "node_id": "node_image_1",
        "kind": "image",
        "output": {
          "type": "image",
          "url": "https://firebasestorage.googleapis.com/.../shot.png"
        }
      }
    ]
  }
}

Notes

  • `nodes` is unchanged and still lists every executed node with its status — `output` is additive.
  • Deliverables are the run's Export nodes. A page with no Export node falls back to the leaves of the executed subgraph (nodes with nothing downstream inside this run), which is what agent-built graphs produce.
  • Nodes that produced no output (pending, failed, skipped) appear in `nodes` only, in neither `output` list.
  • `workflowRev` is the graph revision this run executed, pinned at kickoff. It is `null` for runs started before revisions were pinned.

Reference

Presets

GET /presets
workflows:read

List runnable presets

Lists the published preset workflows anyone can run — the same templates the dashboard's template gallery shows. Each entry carries the inputs you can set, the credit cost of running it at its defaults, and its one-time unlock price if it is a paid preset.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/presets \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "wf_preset_abc",
      "object": "preset",
      "name": "Product hero shot → 6s clip",
      "description": "Turn one product photo into a hero still and a short motion cut.",
      "version": 12,
      "price_credits": 0,
      "category": "ecommerce",
      "tags": [
        "product",
        "video"
      ],
      "inputs": [
        {
          "name": "node_prompt_1.text",
          "node_id": "node_prompt_1",
          "param": "text",
          "label": "Prompt",
          "type": "string",
          "default": "A sneaker on a marble plinth, studio light",
          "description": "Prompt"
        },
        {
          "name": "node_video_1.duration",
          "node_id": "node_video_1",
          "param": "duration",
          "label": "Duration",
          "type": "number",
          "default": 6,
          "description": "Duration"
        }
      ],
      "estimate": 58
    }
  ]
}

Notes

  • `version` is the preset's graph revision. A run pins the version it executed, so a result stays traceable to the exact published version that produced it.
  • `estimate` is the credit cost of running the preset with nothing overridden, computed by the same estimator the run uses. It is `null` if the preset's graph could not be priced.
  • `price_credits` is a ONE-TIME unlock, not a per-run charge. 0 means free.
GET /presets/{presetId}
workflows:read

Get one preset

Returns a single preset with its full input list plus a summary of the nodes in its chain.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

presetId path required

A preset id from `GET /presets`.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/presets/{presetId} \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "id": "wf_preset_abc",
  "object": "preset",
  "name": "Product hero shot → 6s clip",
  "description": "Turn one product photo into a hero still and a short motion cut.",
  "version": 12,
  "price_credits": 0,
  "category": "ecommerce",
  "tags": [
    "product",
    "video"
  ],
  "inputs": [
    {
      "name": "node_prompt_1.text",
      "node_id": "node_prompt_1",
      "param": "text",
      "label": "Prompt",
      "type": "string",
      "default": "A sneaker on a marble plinth, studio light",
      "description": "Prompt"
    }
  ],
  "estimate": 58,
  "nodes": [
    {
      "node_id": "node_prompt_1",
      "kind": "prompt",
      "label": "Prompt"
    },
    {
      "node_id": "node_image_1",
      "kind": "image",
      "label": "Hero still"
    },
    {
      "node_id": "node_video_1",
      "kind": "video",
      "label": "Motion cut"
    },
    {
      "node_id": "node_export_1",
      "kind": "export",
      "label": "Export"
    }
  ]
}

Notes

  • Inputs are derived from the nodes' overridable parameters — exactly the controls the workflow editor renders for each node. Engine bookkeeping fields are never exposed.
POST /presets/{presetId}/estimate
workflows:write

Price a preset run and get a quote

Returns the credit cost of running the preset with your inputs, and a signed price quote you can spend on `POST /presets/{presetId}/runs`. Nothing is created and nothing is charged. The number comes from the same estimator the run uses to set `totalCostCredits`, over the same whole-chain selection, so the quote and the run cannot disagree.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

presetId path required

A preset id from `GET /presets`.

Body Fields

inputs object optional

The same `inputs` object you intend to run with, keyed by an input `name` from `GET /presets/{presetId}`. It is part of what the quote is bound to, so price-changing inputs must be present here.

team_id string optional

Validated exactly as on the run, so a workspace the run would reject cannot be quoted against.

project_id string optional

Validated exactly as on the run.

curl Example

curl https://api.genfire.ai/v1/presets/{presetId}/estimate \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "inputs": {
    "node_prompt_1.text": "A red sneaker on a marble plinth, studio light",
    "node_video_1.duration": 8
  }
}'

Response Example

{
  "object": "cost_estimate",
  "credits": 64,
  "unit": "credits",
  "breakdown": [
    {
      "node_id": "node_prompt_1",
      "kind": "prompt",
      "credits": 0,
      "cached": false
    },
    {
      "node_id": "node_image_1",
      "kind": "image",
      "credits": 8,
      "cached": false
    },
    {
      "node_id": "node_video_1",
      "kind": "video",
      "credits": 56,
      "cached": false
    },
    {
      "node_id": "node_export_1",
      "kind": "export",
      "credits": 0,
      "cached": false
    }
  ],
  "preset_id": "wf_preset_abc",
  "preset_rev": 12,
  "selected_node_ids": [
    "node_prompt_1",
    "node_image_1",
    "node_video_1",
    "node_export_1"
  ],
  "quote_id": "quote_9f1c2ab34d5e6f70",
  "expires_at": "2026-09-12T18:24:00.000Z",
  "quote_token": "eyJxIjoicXVvdGVfOWYxYzJhYjM0ZDVlNmY3MCIsLi4ufQ.f3Kb2p9wQm1sZ0yVxT4uR7NcH6L8dJeA5gXqW0oP1sY"
}

Request Example Body

{
  "inputs": {
    "node_prompt_1.text": "A red sneaker on a marble plinth, studio light",
    "node_video_1.duration": 8
  }
}

Notes

  • Free. No preset copy is instantiated, no run is created, no credits are reserved.
  • Pass `quote_token` back on `POST /presets/{presetId}/runs` to spend the quote. It is bound to the preset id, the preset `version`, and the `inputs` — republishing the preset or changing a price-bearing input invalidates it, and the run replies `409` with a fresh quote rather than charging a stale price.
  • Every node shows `cached: false`: a preset run executes the whole chain rather than reusing saved outputs, which is what makes your inputs take effect. That is also why the estimate is the same whether or not you have run this preset before.
  • A paid preset you have not unlocked returns `402 preset_purchase_required` here too — a quote is never issued for something the run would refuse. Unlock happens on the run, with `confirm_purchase`.
POST /presets/{presetId}/runs
workflows:write

Run a preset

Runs a preset with your inputs. Returns `202 Accepted` with a run id and the workflow id the run belongs to; poll `GET /user-workflows/{workflowId}/runs/{runId}` for progress, per-node outputs and the deliverables split. Credits are charged per node as the chain executes.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

presetId path required

A preset id from `GET /presets`.

Body Fields

inputs object optional

Input values keyed by an input `name` from `GET /presets/{presetId}` (`"<node_id>.<param>"`). A bare `"<param>"` is accepted when exactly one node exposes it. Values must be primitives. Unknown or ambiguous keys are rejected rather than ignored.

team_id string optional

Bill this run to a team pool instead of your own balance. You must be a billable member of the team.

project_id string optional

File the run's image and video outputs into this studio project.

confirm_purchase boolean optional

Required once for a paid preset: confirms the one-time unlock charge of `price_credits`. Later runs of the same preset do not need it.

quote_token string optional

A signed price quote from the matching estimate endpoint, binding this submit to the price you were shown. May also be sent as the `X-Genfire-Quote` header. The token carries the quoted inputs, so it is only spendable on the request it was issued for: 409 `quote_expired` past its 10-minute life, 409 `quote_mismatch` when the inputs or the price no longer match, 401 `quote_invalid` when the signature does not verify. Every 409 carries a FRESH quote in the problem body so you can resubmit in one step.

curl Example

curl https://api.genfire.ai/v1/presets/{presetId}/runs \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "inputs": {
    "node_prompt_1.text": "A red sneaker on a marble plinth, studio light",
    "node_video_1.duration": 8
  }
}'

Response Example

{
  "object": "preset_run",
  "presetId": "wf_preset_abc",
  "presetVersion": 12,
  "workflowId": "wf_your_copy_123",
  "runId": "wfrun_abc123",
  "totalCostCredits": 64,
  "pageId": "page_1",
  "selectedNodeIds": [
    "node_prompt_1",
    "node_image_1",
    "node_video_1",
    "node_export_1"
  ]
}

Request Example Body

{
  "inputs": {
    "node_prompt_1.text": "A red sneaker on a marble plinth, studio light",
    "node_video_1.duration": 8
  }
}

Notes

  • The first run of a preset instantiates your own private copy of it; every later run reuses that copy. No copy is created per run, and the preset author's published workflow is never written to.
  • A new copy is made only when the preset publishes a new `version`, or if you deleted your copy.
  • A preset run executes the WHOLE chain rather than reusing the author's saved outputs — that is what makes your inputs take effect.
  • Paid presets return `402 preset_purchase_required` with the price on the first attempt; retry with `"confirm_purchase": true`. The unlock is one-time and permanent.
  • Poll the run with the `workflowId` from this response, not the preset id.

Reference

Batches

GET /batches
batches:read

List recent batches

Lists batch jobs created by the authenticated account.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

status query optional

Optional status filter such as `queued`, `processing`, `completed`, `failed`, or `partial`.

mode query optional

Optional batch mode filter: `workflow` or `operation`.

target query optional

Optional target filter such as `hook_pack` or `images.generations.create`.

limit query optional

Maximum number of batches to return.

Default: 25

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/batches \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "batch_123",
      "object": "batch",
      "mode": "workflow",
      "target": "hook_pack",
      "status": "queued",
      "total_items": 2,
      "completed_items": 0,
      "failed_items": 0,
      "concurrency": 2,
      "team_id": null,
      "input_summary": {
        "mode": "workflow",
        "target": "hook_pack",
        "item_count": 2
      },
      "output": null,
      "error": null,
      "created_at": "2026-03-25T12:00:00.000Z",
      "updated_at": "2026-03-25T12:00:00.000Z",
      "completed_at": null
    }
  ]
}
GET /batches/{batchId}
batches:read

Get a batch by id

Returns batch-level status, counts, and summary output.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

batchId path required

The batch id returned by `POST /batches`.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/batches/{batchId} \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "id": "batch_123",
  "object": "batch",
  "mode": "workflow",
  "target": "hook_pack",
  "status": "queued",
  "total_items": 2,
  "completed_items": 0,
  "failed_items": 0,
  "concurrency": 2,
  "team_id": null,
  "input_summary": {
    "mode": "workflow",
    "target": "hook_pack",
    "item_count": 2
  },
  "output": null,
  "error": null,
  "created_at": "2026-03-25T12:00:00.000Z",
  "updated_at": "2026-03-25T12:00:00.000Z",
  "completed_at": null
}
GET /batches/{batchId}/items
batches:read

List items in a batch

Returns one row per batch item so you can inspect per-item progress and run ids. Items come back in submission order (`index`), which is assigned once at creation and never changes — including across a retry.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

batchId path required

The batch id returned by `POST /batches`.

status query optional

Optional item status filter: `queued`, `processing`, `completed`, or `failed`. `?status=failed` is the list you feed to the retry endpoint.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/batches/{batchId}/items \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "batch_item_123",
      "object": "batch_item",
      "batch_id": "batch_123",
      "index": 0,
      "custom_id": "blender-professionals",
      "attempt": 0,
      "target": "hook_pack",
      "status": "queued",
      "run_id": null,
      "input_summary": {
        "prompt": "Portable blender for busy professionals"
      },
      "output": null,
      "error": null,
      "created_at": "2026-03-25T12:00:00.000Z",
      "updated_at": "2026-03-25T12:00:00.000Z",
      "completed_at": null
    }
  ]
}
GET /batches/{batchId}/items/{itemId}
batches:read

Get one batch item

Returns a single batch item — the same row shape `GET /batches/{batchId}/items` lists, without pulling the other 49.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

batchId path required

The batch id returned by `POST /batches`.

itemId path required

The batch item id (`batch_item_...`).

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/batches/{batchId}/items/{itemId} \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "id": "batch_item_123",
  "object": "batch_item",
  "batch_id": "batch_123",
  "index": 0,
  "custom_id": "blender-professionals",
  "attempt": 0,
  "target": "hook_pack",
  "status": "queued",
  "run_id": null,
  "input_summary": {
    "prompt": "Portable blender for busy professionals"
  },
  "output": null,
  "error": null,
  "created_at": "2026-03-25T12:00:00.000Z",
  "updated_at": "2026-03-25T12:00:00.000Z",
  "completed_at": null
}
POST /batches/{batchId}/items/{itemId}/retry
batches:write

Retry a failed batch item

Re-runs ONE failed item. The item returns to `processing`, its `attempt` counter increments, and a NEW run is minted for it; the batch counters and status are recomputed, so a `partial` or `failed` batch can go back to `processing` and then reach `completed`. Completed items are never touched.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

batchId path required

The batch id returned by `POST /batches`.

itemId path required

The batch item id (`batch_item_...`). Must be in status `failed`.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/batches/{batchId}/items/{itemId}/retry \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "id": "batch_item_123",
  "object": "batch_item",
  "batch_id": "batch_123",
  "index": 0,
  "custom_id": "blender-professionals",
  "attempt": 1,
  "target": "hook_pack",
  "status": "processing",
  "run_id": "run_456",
  "input_summary": {
    "prompt": "Portable blender for busy professionals"
  },
  "output": null,
  "error": null,
  "created_at": "2026-03-25T12:00:00.000Z",
  "updated_at": "2026-03-25T12:00:00.000Z",
  "completed_at": null
}

Notes

  • A retry BILLS AGAIN, exactly like submitting the generation fresh — the failed attempt was already refunded or never charged, and the new attempt is a new run.
  • Only items in status `failed` can be retried: `409 batch_item_not_retryable` otherwise. That also covers the double-submit case — a second retry arriving while the first is in flight sees `processing` and is refused rather than charged.
  • No `Idempotency-Key` header is needed. Idempotency is per attempt: the item's `attempt` counter is part of the run key, which is what makes the retry mint a new run instead of being handed the original failure back.
  • The item keeps its `index` and `custom_id`, so results still join back to the row you submitted.
POST /batches
batches:write

Create a batch

Create a batch over workflows or selected generation operations. Each item becomes its own run when processed.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Body Fields

mode string required

Use `workflow` for workflow batches or `operation` for supported primitive operations.

target string required

Workflow key or supported operation target such as `images.generations.create`.

concurrency number optional

How many items to process concurrently.

Default: 2

team_id string optional

Bill the whole batch to a team (workspace) pool instead of your own balance. Requires the `teams:read` scope and a billable role on that team.

items array required

Array of batch items. Each item must have an `input` object, and may carry an optional `custom_id`.

curl Example

curl https://api.genfire.ai/v1/batches \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "mode": "workflow",
  "target": "hook_pack",
  "concurrency": 2,
  "items": [
    {
      "custom_id": "blender-professionals",
      "input": {
        "prompt": "Portable blender for busy professionals"
      }
    },
    {
      "custom_id": "blender-students",
      "input": {
        "prompt": "Portable blender for college students"
      }
    }
  ]
}'

Response Example

{
  "id": "batch_123",
  "object": "batch",
  "mode": "workflow",
  "target": "hook_pack",
  "status": "queued",
  "total_items": 2,
  "completed_items": 0,
  "failed_items": 0,
  "concurrency": 2,
  "team_id": null,
  "input_summary": {
    "mode": "workflow",
    "target": "hook_pack",
    "item_count": 2
  },
  "output": null,
  "error": null,
  "created_at": "2026-03-25T12:00:00.000Z",
  "updated_at": "2026-03-25T12:00:00.000Z",
  "completed_at": null,
  "items": [
    {
      "id": "batch_item_123",
      "object": "batch_item",
      "batch_id": "batch_123",
      "index": 0,
      "custom_id": "blender-professionals",
      "attempt": 0,
      "target": "hook_pack",
      "status": "queued",
      "run_id": null,
      "input_summary": {
        "prompt": "Portable blender for busy professionals"
      },
      "output": null,
      "error": null,
      "created_at": "2026-03-25T12:00:00.000Z",
      "updated_at": "2026-03-25T12:00:00.000Z",
      "completed_at": null
    },
    {
      "id": "batch_item_124",
      "object": "batch_item",
      "batch_id": "batch_123",
      "index": 1,
      "custom_id": "blender-students",
      "attempt": 0,
      "target": "hook_pack",
      "status": "queued",
      "run_id": null,
      "input_summary": {
        "prompt": "Portable blender for college students"
      },
      "output": null,
      "error": null,
      "created_at": "2026-03-25T12:00:00.000Z",
      "updated_at": "2026-03-25T12:00:00.000Z",
      "completed_at": null
    }
  ]
}

Request Example Body

{
  "mode": "workflow",
  "target": "hook_pack",
  "concurrency": 2,
  "items": [
    {
      "custom_id": "blender-professionals",
      "input": {
        "prompt": "Portable blender for busy professionals"
      }
    },
    {
      "custom_id": "blender-students",
      "input": {
        "prompt": "Portable blender for college students"
      }
    }
  ]
}

Notes

  • Operation batches support: images.generations.create, videos.generations.create, audio.speech.create. (`audio.speech.create` is speech only — music and sound effects are not batchable.)
  • Workflow batches support any workflow listed by `GET /workflows`, including the async ones (`ugc_ad_video`, `ad_remix`): those items stay `processing` until their run finishes, and the batch closes when every item is terminal.
  • Items in `operation`-mode batches accept the same body fields as the operation `target` — e.g. `quality` and `resolution` are honored when batching `images.generations.create`. The validation is literally the same code the single-shot endpoint runs.
  • Optional per-item `custom_id`: at most 64 characters and unique within the batch. It is echoed on every item row, so you can join results back to your own records without tracking Genfire ids.
  • Each item also keeps a server-assigned `index` — its position in the `items` array you submitted. It is assigned once and never recomputed, including across a retry.
  • Per-item `project_id` files that item's output into a studio project; it is resolved per item, so one bad id never fails the batch.

Reference

Webhooks

GET /webhooks
webhooks:read

List webhook endpoints

Returns webhook destinations registered for the authenticated account.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/webhooks \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "wh_123",
      "object": "webhook_endpoint",
      "url": "https://example.com/webhooks/genfire",
      "description": "Agency production endpoint",
      "status": "active",
      "events": [
        "run.completed",
        "run.failed"
      ],
      "signing_secret_preview": "gfwsec_1234...abcd",
      "created_at": "2026-03-25T12:00:00.000Z",
      "updated_at": "2026-03-25T12:00:00.000Z",
      "last_delivery_at": null
    }
  ]
}
GET /webhooks/deliveries
webhooks:read

List webhook deliveries

Returns delivery attempts for the authenticated account.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

endpoint_id query optional

Optional webhook endpoint id to filter deliveries.

limit query optional

Maximum number of deliveries to return.

Default: 25

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/webhooks/deliveries \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "delivery_123",
      "object": "webhook_delivery",
      "endpoint_id": "wh_123",
      "event_type": "run.completed",
      "run_id": "run_img_123",
      "batch_id": null,
      "status": "success",
      "attempt_count": 1,
      "max_attempts": 6,
      "response_status": 200,
      "response_body": "ok",
      "last_error": null,
      "request_headers": {
        "X-Genfire-Signature": "sha256=..."
      },
      "created_at": "2026-03-25T12:00:05.000Z",
      "updated_at": "2026-03-25T12:00:05.000Z",
      "last_attempt_at": "2026-03-25T12:00:05.000Z",
      "next_attempt_at": null,
      "delivered_at": "2026-03-25T12:00:05.000Z"
    }
  ]
}
POST /webhooks
webhooks:write

Create a webhook endpoint

Registers a webhook destination and returns the signing secret once.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

url string required

Public HTTPS endpoint that will receive webhook POST requests.

description string optional

Optional human-readable label for the endpoint.

events array optional

Optional list of event types to subscribe to.

Default: ["run.completed","run.failed"]

curl Example

curl https://api.genfire.ai/v1/webhooks \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://example.com/webhooks/genfire",
  "description": "Agency production endpoint",
  "events": [
    "run.completed",
    "run.failed"
  ]
}'

Response Example

{
  "id": "wh_123",
  "object": "webhook_endpoint",
  "url": "https://example.com/webhooks/genfire",
  "description": "Agency production endpoint",
  "status": "active",
  "events": [
    "run.completed",
    "run.failed"
  ],
  "signing_secret_preview": "gfwsec_1234...abcd",
  "created_at": "2026-03-25T12:00:00.000Z",
  "updated_at": "2026-03-25T12:00:00.000Z",
  "last_delivery_at": null,
  "signing_secret": "gfwsec_live_example_secret"
}

Request Example Body

{
  "url": "https://example.com/webhooks/genfire",
  "description": "Agency production endpoint",
  "events": [
    "run.completed",
    "run.failed"
  ]
}

Notes

  • Store the signing secret immediately. It is only returned when the endpoint is created.
POST /webhooks/deliveries/{deliveryId}/replay
webhooks:write

Replay a webhook delivery

Queues a new delivery attempt for an existing webhook delivery record.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

deliveryId path required

The webhook delivery id.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/webhooks/deliveries/{deliveryId}/replay \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "id": "delivery_456",
  "object": "webhook_delivery",
  "endpoint_id": "wh_123",
  "event_type": "run.completed",
  "run_id": "run_img_123",
  "batch_id": null,
  "status": "pending",
  "attempt_count": 0,
  "max_attempts": 6,
  "response_status": null,
  "response_body": null,
  "last_error": null,
  "request_headers": {
    "X-Genfire-Signature": "sha256=..."
  },
  "created_at": "2026-03-25T12:00:05.000Z",
  "updated_at": "2026-03-25T12:00:05.000Z",
  "last_attempt_at": null,
  "next_attempt_at": null,
  "delivered_at": null
}
PATCH /webhooks/{endpointId}
webhooks:write

Update a webhook endpoint

Update a webhook URL, description, status, or subscribed events.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

endpointId path required

The webhook endpoint id.

Body Fields

url string optional

New destination URL.

description string | null optional

New description or `null` to clear it.

status string optional

Use `active` or `disabled`.

events array optional

Replacement list of subscribed event types.

curl Example

curl https://api.genfire.ai/v1/webhooks/{endpointId} \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "status": "disabled"
}'

Response Example

{
  "id": "wh_123",
  "object": "webhook_endpoint",
  "url": "https://example.com/webhooks/genfire",
  "description": "Agency production endpoint",
  "status": "disabled",
  "events": [
    "run.completed",
    "run.failed"
  ],
  "signing_secret_preview": "gfwsec_1234...abcd",
  "created_at": "2026-03-25T12:00:00.000Z",
  "updated_at": "2026-03-25T12:05:00.000Z",
  "last_delivery_at": null
}

Request Example Body

{
  "status": "disabled"
}
DELETE /webhooks/{endpointId}
webhooks:write

Delete a webhook endpoint

Deletes the webhook endpoint. The endpoint id is no longer usable after deletion.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

endpointId path required

The webhook endpoint id.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/webhooks/{endpointId} \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

204 No Content

Reference

Picture Books

POST /picture-books/generations
images:write

Generate an illustrated picture book

Creates a print-ready children’s book: a style anchor, a reference sheet per cast member and recurring place, then every page painted from those references so the characters stay the same, and finally the covers (which render LAST, from a finished interior page). Async — returns a run in `processing`; poll `GET /v1/runs/{runId}` (in-flight runs carry `progress`, and `resource_id` is the book id), then read `GET /v1/picture-books/{id}` for the pages. Pass `plan` to have your own words rendered exactly as written; pass `idea` or `script` to have Genfire write the story first.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Body Fields

plan object optional

An authored book: `{ title, cast:[{name, description}], places?:[{name, description}], pages:[{ text, visual, cast?, place?, kind? }] }`. Rendered exactly as written — no planner runs. `kind` is `page` | `spread` (one picture across two facing pages, one render, one page credit, must start on an even slot) | `text-page` (words only, free). Overrides `idea`/`script`/`pages`.

idea string optional

One line to write the story from, when you are not supplying `plan` or `script`.

script string optional

Your full story text (≤ 20000 chars). Split across pages by the planner; the words themselves are never rewritten.

title string optional

Book title. Defaults to the planner’s.

cast array optional

Up to 4 characters as `{name, description}` — one fixed physical description each, which becomes that character’s reference sheet.

age_band string optional

`board` | `picture` (default) | `early-reader`. Sets the word budget and the page-count options.

pages number optional

Interior page count within the band’s options (board 12/16/20, picture 16/24/32/40, early-reader 24/32/40/48).

style_id string optional

Art style from `GET /v1/picture-books/styles`. Defaults to `storybook`.

format_id string optional

Trim/aspect preset (KDP trims, other print sizes, or digital aspects). Defaults to `kdp-8.5x8.5`.

quality string optional

`low` (default) or `medium`. Medium costs several times more per unit — confirm before choosing it.

lettering string optional

`typeset` (default) — Genfire sets the words in post, so they stay editable and correctly spelled — or `lettered`, where the model paints them into the art. The FRONT COVER is always lettered.

team_id string optional

Bill the team pool (requires a billable role on that team).

curl Example

curl https://api.genfire.ai/v1/picture-books/generations \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "idea": "a shy hedgehog who is afraid of the dark and finds a firefly friend",
  "age_band": "picture",
  "pages": 24,
  "style_id": "storybook",
  "quality": "low"
}'

Response Example

{
  "id": "run_123",
  "object": "run",
  "status": "processing",
  "capability": "picture_book_generation",
  "output": {
    "book_id": "bk_123",
    "url": "https://genfire.ai/dashboard/books/bk_123"
  }
}

Request Example Body

{
  "idea": "a shy hedgehog who is afraid of the dark and finds a firefly friend",
  "age_band": "picture",
  "pages": 24,
  "style_id": "storybook",
  "quality": "low"
}

Notes

  • Call `POST /v1/picture-books/estimate-cost` first — the whole book is checked for affordability up front and a short balance is a 402 before anything renders.
  • Every page is billed as one unit; a spread is ONE render and one page credit; a `text-page` is free.
  • Covers render last so they match the finished interior.
POST /picture-books/estimate-cost
account:read

Estimate picture-book credit cost

Prices a prospective book without generating anything: the style anchor, one sheet per cast member and place, every art page and the two covers. Also returns the plan Genfire would build (title, cast, page count) so you can confirm it first.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

idea string optional

Same body as the generation call — `idea`, `script` or `plan`, plus the config fields.

age_band string optional

Sets the page-count options and the word budget.

pages number optional

Interior page count.

quality string optional

`low` or `medium`.

curl Example

curl https://api.genfire.ai/v1/picture-books/estimate-cost \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "idea": "a shy hedgehog who is afraid of the dark",
  "age_band": "picture",
  "pages": 24,
  "quality": "low"
}'

Response Example

{
  "object": "picture_book_cost_estimate",
  "estimated_credits": 84,
  "current_credits": 1200,
  "affordable": true,
  "plan": {
    "title": "Pip and the Little Light",
    "pages": 24,
    "art_pages": 24,
    "cast": [
      {
        "name": "PIP",
        "description": "a small round hedgehog with a rust-red scarf"
      }
    ]
  }
}

Request Example Body

{
  "idea": "a shy hedgehog who is afraid of the dark",
  "age_band": "picture",
  "pages": 24,
  "quality": "low"
}
GET /picture-books/{id}
account:read

Read one picture book

The book: status, progress, cast and places, every page with its art URL and text, the exports produced so far, and a deep link into the studio.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/picture-books/{id} \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "picture_book",
  "id": "bk_123",
  "title": "Pip and the Little Light",
  "status": "ready",
  "pages": [
    {
      "id": "pg_1",
      "page_number": 1,
      "kind": "page",
      "text": "Pip was small.",
      "image_url": "https://…/page-1.png",
      "status": "ready"
    }
  ]
}
GET /picture-books/styles
account:read

List picture-book art styles

The sixteen art styles that can be passed as `style_id`, plus the trim/aspect presets for `format_id`.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/picture-books/styles \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "storybook",
      "label": "Classic Storybook",
      "group": "Painterly"
    }
  ]
}

Reference

Faceless Reels

POST /faceless-reels/generations
reels:write

Generate a faceless reel

Generates a vertical (9:16) short end-to-end: LLM script → voiceover → style-locked images → music → captioned video. Async — returns a run in `processing`; poll `GET /v1/runs/{runId}` until it is `completed`, then read the `video_url` from `output`.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Body Fields

topic string required

The subject / seed for the reel. Use a phrase like "the disappearance of the Sodder children" or "Surprise me with a fresh idea".

preset_id string optional

Niche preset id from `GET /v1/faceless-reels/presets` (drives topic + tone). Defaults to a creepy-story preset.

style_id string optional

Visual style id from `GET /v1/faceless-reels/styles`. Defaults to the preset’s recommended style.

target_duration_sec number optional

Target length in seconds (10–600, up to 10 minutes). Drives script + scene count. Defaults to the niche’s natural length.

caption_preset_id string optional

Caption font/animation preset from `GET /v1/faceless-reels/caption-presets`.

Default: hormozi

caption_animation string optional

Override the caption animation (highlight | pop | typewriter | classic | background).

caption_position string optional

Vertical caption placement: "top" | "middle" | "bottom". Default middle (the classic centered render).

Default: middle

voice_id string optional

TTS voice id (ElevenLabs or Qwen). Defaults to a built-in narrator voice.

motion_vibe string optional

Camera-motion feel for the image slideshow: "auto" (per-niche, default) | "calm" (slow pans, subtle zoom) | "dynamic" (mixed pans + zooms) | "energetic" (corner punch-ins + shake).

Default: auto

animation_mode string optional

How much of the reel is real video: "stills" (Ken-Burns slideshow, default) | "hook" (premium — the FIRST scene is a real omni image-to-video clip) | "full" (ultra — EVERY scene is a real omni clip, one i2v clip cost per scene). Wins over animated_hook.

Default: stills

animated_hook boolean optional

LEGACY: true ≈ animation_mode "hook". Prefer animation_mode.

Default: false

fast_mode boolean optional

Fast mode: animated scenes render on MiniMax H3 Max Turbo (4 cr/s at 768P, 5–15s clips) instead of Omni Flash 1.1 (10 cr/s at 720p). Scenes that feature reference images or cast sheets stay on omni's reference tier (Turbo has none). Faster and cheaper per animated scene; clips carry no generated ambience.

Default: false

video_model string optional

i2v model for animated clips. Every value resolves to "omni" (gemini-omni-flash); "grok"/"seedance-mini" are accepted legacy aliases.

Default: omni

direction string optional

Extra creative direction passed to the script model.

custom_story object optional

{ prompt, scene_hint? } to author a fully custom story instead of using a niche preset.

script object optional

Agent-authored structured script — the SAME contract as `POST /v1/explainers/generations` (`{ hook_text?, cast?, beats: [{ narration, visual, motion?, emphasis?, continues? }] }`). When present the engine voices your beats verbatim, skips its own writer, and `target_duration_sec` is ignored (the spoken words set the runtime, 10–600s). `hook_text` (3-6 words) renders as the on-screen hook card over the first ~1.5s. Validation errors return 400 `invalid_script` naming the field.

music object optional

Background music: { source: "none"|"preset"|"ai"|"library", preset_id?, prompt?, track_id? }.

team_id string optional

Bill this run to a WORKSPACE (team) credit pool instead of your balance (`GET /v1/teams`). Membership and a spending role are asserted before the run is created — 404 `invalid_team_id` when you are not a member, 403 `team_role_cannot_spend` when your role cannot spend. Requires the `teams:read` scope. A route that cannot bill a pool yet returns 400 `team_billing_unsupported` rather than quietly charging you. The run echoes it back as `team_id`.

quote_token string optional

A signed price quote from the matching estimate endpoint, binding this submit to the price you were shown. May also be sent as the `X-Genfire-Quote` header. The token carries the quoted inputs, so it is only spendable on the request it was issued for: 409 `quote_expired` past its 10-minute life, 409 `quote_mismatch` when the inputs or the price no longer match, 401 `quote_invalid` when the signature does not verify. Every 409 carries a FRESH quote in the problem body so you can resubmit in one step.

curl Example

curl https://api.genfire.ai/v1/faceless-reels/generations \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "topic": "The unsolved mystery of the Mary Celeste",
  "preset_id": "mystery",
  "target_duration_sec": 45,
  "caption_preset_id": "hormozi",
  "music": {
    "source": "preset",
    "preset_id": "unsolved-mystery"
  },
  "quote_id": "quote_9f1c2ab34d5e6f70",
  "expires_at": "2026-09-12T18:24:00.000Z",
  "quote_token": "eyJxIjoicXVvdGVfOWYxYzJhYjM0ZDVlNmY3MCIsLi4ufQ.f3Kb2p9wQm1sZ0yVxT4uR7NcH6L8dJeA5gXqW0oP1sY"
}'

Response Example

{
  "id": "run_8f3c4a2b1d6e7f9a0b1c2d3e",
  "object": "run",
  "status": "processing",
  "capability": "faceless_reel_generation",
  "endpoint": "faceless_reels.generations.create",
  "model": "reel.faceless",
  "resource_id": "reel_abc123",
  "created_at": "2026-06-08T12:00:00.000Z"
}

Request Example Body

{
  "topic": "The unsolved mystery of the Mary Celeste",
  "preset_id": "mystery",
  "target_duration_sec": 45,
  "caption_preset_id": "hormozi",
  "music": {
    "source": "preset",
    "preset_id": "unsolved-mystery"
  },
  "quote_id": "quote_9f1c2ab34d5e6f70",
  "expires_at": "2026-09-12T18:24:00.000Z",
  "quote_token": "eyJxIjoicXVvdGVfOWYxYzJhYjM0ZDVlNmY3MCIsLi4ufQ.f3Kb2p9wQm1sZ0yVxT4uR7NcH6L8dJeA5gXqW0oP1sY"
}

Notes

  • WRITING FOR RETENTION (what the internal writer is held to — hold yourself to it): (1) Most viewers arrive muted: give `hook_text` (3-6 words, ≤32 chars) a muted viewer cannot scroll past — a claim, number, contradiction or open loop, not a title. (2) Beat 1 opens mid-action on the single most shocking concrete detail — never "Did you know", "Imagine", "What if", a greeting or a restatement — and its visual is a MOMENT OF CHANGE (something opening, falling, breaking, revealed), never a portrait. (3) Beat jobs by position: hook → promise (what they get, without giving it) → escalate → RE-HOOK at ~40% where retention sags (a reversal / "but then") → payoff, with a last sentence that earns a replay or a comment, never a sign-off. (4) One scene every ~4s for shorts; present tense; concrete names, numbers, sensory detail; every narration a complete sentence. (5) `emphasis` on most beats: 1-3 VERBATIM narration words that should pop on screen. (6) Facts formats: order by impact, keep the most jaw-dropping verified fact for last, no manufactured twists; never invent a specific you are not sure of.
  • A reel renders in minutes. Poll `GET /v1/runs/{runId}` until `completed`; the output carries `{ reel_id, video_url, script, scenes, duration_seconds }`.
  • Cost varies with duration and music. Call `POST /v1/faceless-reels/estimate-cost` first for a per-config credit estimate.
  • The same `Idempotency-Key` returns the same run instead of generating (and billing) twice.
POST /faceless-reels/estimate-cost
reels:read

Estimate reel credit cost

Returns a per-config credit breakdown (images + voiceover + music) for a prospective reel, without generating anything.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

preset_id string optional

Niche preset id (affects default scene count).

target_duration_sec number optional

Target length in seconds (10–600, up to 10 minutes).

music object optional

Music config; only `source: "ai"` adds a music fee.

curl Example

curl https://api.genfire.ai/v1/faceless-reels/estimate-cost \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "preset_id": "mystery",
  "target_duration_sec": 45,
  "music": {
    "source": "preset"
  }
}'

Response Example

{
  "object": "reel_cost_estimate",
  "images": 18,
  "voiceover": 6,
  "music": 0,
  "total": 24,
  "sceneCount": 7,
  "quote_id": "quote_9f1c2ab34d5e6f70",
  "expires_at": "2026-09-12T18:24:00.000Z",
  "quote_token": "eyJxIjoi...fQ.f3Kb2p9w"
}

Request Example Body

{
  "preset_id": "mystery",
  "target_duration_sec": 45,
  "music": {
    "source": "preset"
  }
}
GET /faceless-reels/presets
reels:read

List niche presets

Lists the available niche presets (topic + tone) that can be passed as `preset_id`.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/faceless-reels/presets \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "mystery",
      "label": "Unsolved Mystery",
      "emoji": "🔍",
      "tone": "mystery",
      "recommendedStyleId": "cinematic",
      "preview_url": "https://storage.googleapis.com/genfire-2.firebasestorage.app/faceless-reels/previews/mystery.png",
      "preview_video_url": "https://storage.googleapis.com/genfire-2.firebasestorage.app/style-previews/faceless-reel-presets/mystery.mp4"
    }
  ]
}

Notes

  • `preview_url` is a still; `preview_video_url` is a short looping clip that may not exist for every entry yet (treat a 404 as “no clip”, fall back to the still).
GET /faceless-reels/styles
reels:read

List visual styles

Lists the available visual styles (art mediums) that can be passed as `style_id`.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/faceless-reels/styles \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "cinematic",
      "label": "Cinematic",
      "emoji": "🎬",
      "group": "Realistic",
      "preview_url": "https://storage.googleapis.com/genfire-2.firebasestorage.app/faceless-reels/styles/cinematic.png",
      "preview_video_url": "https://storage.googleapis.com/genfire-2.firebasestorage.app/style-previews/faceless-reels/cinematic.mp4"
    }
  ]
}

Notes

  • `preview_url` is a still; `preview_video_url` is a short looping clip that may not exist for every style yet (treat a 404 as “no clip”, fall back to the still).
GET /faceless-reels/music-presets
reels:read

List background-music presets

Lists curated background-music tracks usable with `music.source = "preset"`.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/faceless-reels/music-presets \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "unsolved-mystery",
      "label": "Unsolved Mystery",
      "mood": "mystery"
    }
  ]
}
GET /faceless-reels/caption-presets
reels:read

List caption presets

Lists caption font/animation presets usable as `caption_preset_id`.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/faceless-reels/caption-presets \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "hormozi",
      "label": "Hormozi",
      "animation": "highlight"
    }
  ]
}
GET /faceless-reels/subscriptions
reels:read

List reel subscriptions

Lists the account owner’s recurring reel subscriptions ("Stories"). Each defines a niche + schedule that auto-generates reels.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/faceless-reels/subscriptions \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "sub_abc123",
      "enabled": true,
      "presetId": "mystery",
      "cadencePerDay": 1,
      "slots": [
        "18:00"
      ],
      "timezone": "America/New_York"
    }
  ]
}
POST /faceless-reels/subscriptions
reels:write

Create a reel subscription

Creates a faceless CHANNEL — a named recurring series that auto-produces episodes on a daily schedule. This is the same object the Faceless Studio shows; the path says "subscriptions" for backwards compatibility. Accepts the same creative fields as one-off generation, plus the channel identity, scheduling and topic-rotation fields. Pass enabled:false for a channel with no schedule yet.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

label string optional

The channel name.

avatar_url string optional

CHANNEL identity: square avatar image URL.

tagline string optional

CHANNEL identity: one-line pitch shown under the channel name.

description string optional

CHANNEL identity: longer channel description.

niche string optional

CHANNEL identity: niche id (education, history, kids, storytelling, true-crime, finance, motivation, science, mystery, horror, animals, mythology, psychology).

format string optional

CHANNEL identity: "shorts" (9:16 reels) or "longform" (16:9 explainer episodes) — picks which engine renders each episode.

Default: shorts

episode_defaults object optional

CHANNEL identity: defaults every new episode inherits — { aspect_ratio: "16:9"|"9:16", target_duration_sec: 8-600, motion_style: "seamless"|"scenes"|"stills" }.

preset_id string optional

Niche preset id.

style_id string optional

Visual style id.

caption_preset_id string optional

Caption preset id.

caption_position string optional

Vertical caption placement: "top" | "middle" | "bottom".

Default: middle

voice_id string optional

TTS voice id.

motion_vibe string optional

Camera-motion feel: "auto" | "calm" | "dynamic" | "energetic".

Default: auto

animation_mode string optional

Animation tier: "stills" | "hook" (first scene animated) | "full" (every scene a real omni clip).

Default: stills

animated_hook boolean optional

LEGACY: true ≈ animation_mode "hook".

Default: false

fast_mode boolean optional

Fast mode: animated scenes render on MiniMax H3 Max Turbo (4 cr/s at 768P, 5–15s clips) instead of Omni Flash 1.1 (10 cr/s at 720p). Scenes that feature reference images or cast sheets stay on omni's reference tier (Turbo has none). Faster and cheaper per animated scene; clips carry no generated ambience.

Default: false

video_model string optional

i2v model for animated clips (every value resolves to "omni").

Default: omni

target_duration_sec number optional

Target length (10–600, up to 10 minutes).

music object optional

Background music config.

topic_source string optional

"ai-auto" (fresh ideas) or "user-list" (rotate `topic_seeds`).

Default: ai-auto

topic_seeds array optional

Topics to rotate through when `topic_source` is "user-list".

cadence_per_day number optional

Reels per day (1–6).

Default: 1

slots array optional

Local "HH:mm" times; length must equal `cadence_per_day`.

Default: ["18:00"]

timezone string optional

IANA timezone for the slots.

Default: America/New_York

direction string optional

STANDING creative direction every episode inherits — tone, pacing, what to avoid.

custom_story object optional

{ prompt, scene_hint? } — a fixed recurring premise every episode is written from.

reference_images array optional

The channel’s RECURRING CAST: [{ url, label }] up to 8 https images every episode inherits.

reference_url string optional

A reference channel this one is modelled on.

caption_animation string optional

highlight | pop | typewriter | classic | background.

caption_words_per_line number optional

Caption line length (1-12).

ideas array optional

Episode titles queued for this channel (up to 24).

targets array optional

Connected accounts every finished episode AUTO-PUBLISHES to, as "platform:account_id" (see GET /social/accounts). Omit to keep episodes private.

enabled boolean optional

Whether the schedule is active.

Default: true

curl Example

curl https://api.genfire.ai/v1/faceless-reels/subscriptions \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "label": "Daily mysteries",
  "preset_id": "mystery",
  "cadence_per_day": 1,
  "slots": [
    "18:00"
  ],
  "timezone": "America/New_York",
  "topic_source": "ai-auto"
}'

Response Example

{
  "object": "reel_subscription",
  "id": "sub_abc123",
  "enabled": true,
  "presetId": "mystery",
  "cadencePerDay": 1,
  "slots": [
    "18:00"
  ],
  "timezone": "America/New_York"
}

Request Example Body

{
  "label": "Daily mysteries",
  "preset_id": "mystery",
  "cadence_per_day": 1,
  "slots": [
    "18:00"
  ],
  "timezone": "America/New_York",
  "topic_source": "ai-auto"
}
PATCH /faceless-reels/subscriptions/{id}
reels:write

Update a reel subscription

Updates a faceless channel. Accepts every field the create endpoint does — including the channel identity (avatar_url, tagline, description, niche, format, episode_defaults). Changing cadence/slots/timezone recomputes the schedule; enabled:false pauses it without deleting it.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

id path required

Subscription id.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/faceless-reels/subscriptions/{id} \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "enabled": false
}'

Response Example

{
  "object": "reel_subscription",
  "id": "sub_abc123",
  "enabled": false
}

Request Example Body

{
  "enabled": false
}
DELETE /faceless-reels/subscriptions/{id}
reels:write

Delete a reel subscription

Deletes a reel subscription. Already-generated reels are unaffected.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

id path required

Subscription id.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/faceless-reels/subscriptions/{id} \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "reel_subscription",
  "id": "sub_abc123",
  "deleted": true
}
POST /faceless-reels/subscriptions/{id}/estimate-episode
reels:read

Quote one channel episode

Resolves an episode against the channel and returns the PLAN plus a credit estimate, without generating anything. The plan is what will actually be made: which engine won (a `longform` channel, a 16:9 aspect_ratio, or a custom_script all force the explainer), the clamped duration, and the style, voice and reference images that applied. Quote before POST /episodes and confirm the spend.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

id path required

Channel id.

Body Fields

topic string optional

Episode title or subject.

aspect_ratio string optional

"16:9" | "9:16". Overrides the channel default; 16:9 forces the explainer engine.

target_duration_sec number optional

Explainer 20-600, reel 10-120. Clamped to the winning engine.

motion_style string optional

"seamless" | "scenes" | "stills".

curl Example

curl https://api.genfire.ai/v1/faceless-reels/subscriptions/{id}/estimate-episode \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "topic": "How Carthage fell",
  "aspect_ratio": "16:9",
  "target_duration_sec": 300,
  "motion_style": "seamless"
}'

Response Example

{
  "object": "episode_estimate",
  "plan": {
    "engine": "explainer",
    "aspect_ratio": "16:9",
    "target_duration_sec": 300,
    "motion_style": "seamless",
    "style_id": "pixel-art",
    "voice_id": "onwK4e9ZLuTAKqWW03F9",
    "caption_preset_id": "none",
    "reference_images": []
  },
  "estimate": {
    "total": 812
  }
}

Request Example Body

{
  "topic": "How Carthage fell",
  "aspect_ratio": "16:9",
  "target_duration_sec": 300,
  "motion_style": "seamless"
}
GET /faceless-reels/subscriptions/{id}/episodes
reels:read

List a channel’s episodes

Episodes filed under this channel, newest first — reels AND long-form explainer episodes, whichever engine produced them.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Parameters

id path required

Channel id.

limit query optional

1-100, default 30.

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/faceless-reels/subscriptions/{id}/episodes \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "reel_9x1",
      "object": "episode",
      "topic": "How Carthage fell",
      "status": "completed",
      "kind": "explainer",
      "aspect_ratio": "16:9",
      "video_url": "https://storage.googleapis.com/genfire/episodes/9x1.mp4",
      "thumbnail_url": null,
      "created_at": "2026-08-27T09:00:00.000Z"
    }
  ]
}
POST /faceless-reels/subscriptions/{id}/episodes
reels:write

Produce one channel episode

Produces ONE episode of a channel with per-episode overrides — the editorial call, as opposed to run-now which fires the channel’s next episode on its own settings. The channel’s `format` picks the engine (shorts → 9:16 faceless reel, longform → 16:9 explainer) and the episode inherits the channel’s creative lock (style, voice, standing direction, recurring cast, captions, music) unless overridden here. Async and billable — quote with estimate-episode first. Returns 409 if the same title is already generating on this channel.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Parameters

id path required

Channel id.

Body Fields

topic string required

Episode title or subject.

aspect_ratio string optional

"16:9" | "9:16".

target_duration_sec number optional

Explainer 20-600, reel 10-120.

motion_style string optional

"seamless" chains each beat off the last frame within a sequence; "scenes" is one shot per beat; "stills" is no motion.

fast_mode boolean optional

Fast mode: animated scenes render on MiniMax H3 Max Turbo (4 cr/s at 768P, 5–15s clips) instead of Omni Flash 1.1 (10 cr/s at 720p). Scenes that feature reference images or cast sheets stay on omni's reference tier (Turbo has none). Faster and cheaper per animated scene; clips carry no generated ambience.

Default: false

style_id string optional

Override the channel style for this episode.

voice_id string optional

Override the channel narrator for this episode.

reference_images array optional

[{ url, label }] up to 8 — added on top of the channel’s recurring cast.

custom_script string optional

Your own narration instead of the writer’s. Forces the explainer engine and sets the runtime.

custom_style_prompt string optional

Describe the look instead of picking a style_id.

style_anchor_url string optional

An image that becomes frame 0 (the style anchor). Not a reference image.

captions_on boolean optional

Burn captions. Default: on for shorts, off for long-form.

music object optional

{ source: none|preset|ai|library, preset_id?, prompt?, track_id? }.

engine string optional

"reel" | "explainer" — force the engine instead of deriving it.

curl Example

curl https://api.genfire.ai/v1/faceless-reels/subscriptions/{id}/episodes \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "topic": "How Carthage fell",
  "aspect_ratio": "16:9",
  "target_duration_sec": 300,
  "motion_style": "seamless"
}'

Response Example

{
  "id": "run_8f3c4a2b1d6e7f9a0b1c2d3e",
  "object": "run",
  "status": "processing",
  "capability": "explainer_generation"
}

Request Example Body

{
  "topic": "How Carthage fell",
  "aspect_ratio": "16:9",
  "target_duration_sec": 300,
  "motion_style": "seamless"
}
POST /faceless-reels/subscriptions/{id}/run-now
reels:write

Generate a reel now for a subscription

Manually triggers one reel for a subscription right now (async run), using the subscription’s settings. Returns 409 if a reel is already generating for that subscription.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Parameters

id path required

Subscription id.

Body Fields

topic string optional

Optional topic override for this run; otherwise resolved from the subscription.

curl Example

curl https://api.genfire.ai/v1/faceless-reels/subscriptions/{id}/run-now \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "topic": "The vanishing of Flight 19"
}'

Response Example

{
  "id": "run_8f3c4a2b1d6e7f9a0b1c2d3e",
  "object": "run",
  "status": "processing",
  "capability": "faceless_reel_generation",
  "endpoint": "faceless_reels.subscriptions.run_now",
  "model": "reel.faceless",
  "resource_id": "reel_abc123",
  "created_at": "2026-06-08T12:00:00.000Z",
  "quote_id": "quote_9f1c2ab34d5e6f70",
  "expires_at": "2026-09-12T18:24:00.000Z",
  "quote_token": "eyJxIjoicXVvdGVfOWYxYzJhYjM0ZDVlNmY3MCIsLi4ufQ.f3Kb2p9wQm1sZ0yVxT4uR7NcH6L8dJeA5gXqW0oP1sY"
}

Request Example Body

{
  "topic": "The vanishing of Flight 19"
}

Notes

  • Poll `GET /v1/runs/{runId}` for the finished video, same as one-off generation.

Reference

Scheduled Tasks

GET /tasks
tasks:read

List scheduled tasks

Lists the account owner’s scheduled tasks — standing briefs Genfire’s agent runs on its own cadence, headlessly, filing each result for review.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/tasks \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "object": "scheduled_task",
      "id": "task_abc123",
      "title": "Daily affiliate report",
      "cadence": "daily",
      "active": true,
      "next_run_at": "2026-08-27T19:40:00.000Z",
      "max_credits_per_run": 100,
      "auto_approve": false,
      "delivery": {
        "email": true,
        "slack": false
      }
    }
  ]
}
POST /tasks
tasks:write

Create a scheduled task

Schedules a brief the agent runs automatically. Creating one is free; each RUN spends credits, capped by max_credits_per_run. The brief must be self-contained — a scheduled run has no conversation to read. Give the start time either as `time` + `timezone` (a local wall clock) or `first_run_at` (an exact instant); an unreadable value is a 400 rather than a silent default, because a report firing at the wrong hour every day is worse than one that refused to be created.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

prompt string required

The full standing brief the run executes.

title string optional

Short name for the schedule list.

cadence string optional

once | daily | weekly | monthly.

Default: daily

time string optional

Local start on a 24-HOUR clock, "HH:mm" — 3:40pm is "15:40". Requires timezone.

timezone string optional

IANA zone for `time`, e.g. "America/New_York".

first_run_at string optional

Alternative to time+timezone: an ISO 8601 instant with offset. Must be in the future.

max_credits_per_run integer optional

Credit ceiling ONE run may spend (1-2000).

Default: 100

auto_approve boolean optional

Autopilot — runs publish within budget instead of waiting for the owner’s sign-off.

Default: false

persona_id string optional

AI Employee to run the task as.

delivery object optional

{ email, slack } — where a finished run reports. Both default false.

curl Example

curl https://api.genfire.ai/v1/tasks \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "scheduled_task",
  "id": "task_abc123",
  "title": "Daily affiliate report",
  "cadence": "daily",
  "active": true,
  "next_run_at": "2026-08-27T19:40:00.000Z",
  "runs_at": "3:40pm America/New_York"
}
PATCH /tasks/{id}
tasks:write

Pause, resume or re-route a scheduled task

Pausing (active:false) is the reversible way to stop a task. A `delivery` patch merges onto the stored channels, so naming one does not switch the other off. The brief, cadence and start time are not editable — delete and recreate to change those.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

active boolean optional

false pauses, true resumes (also clears an automatic failure pause).

delivery object optional

{ email, slack } — only the channels you name change.

curl Example

curl https://api.genfire.ai/v1/tasks/{id} \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "scheduled_task",
  "id": "task_abc123",
  "active": false
}
DELETE /tasks/{id}
tasks:write

Delete a scheduled task

Permanently deletes the task. Not reversible — to stop it temporarily, PATCH active:false instead. Runs it already produced are unaffected.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/tasks/{id} \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "scheduled_task",
  "id": "task_abc123",
  "deleted": true
}

Reference

Explainers

POST /explainers/generations
reels:write

Generate an explainer video

Generates a long-form narrated explainer/documentary (20s–10min, 16:9 or 9:16): script → expressive voiceover → style-locked frames → Gemini Omni Flash video clips per scene → composed film with optional captions. Pass a structured `script` to author the ENTIRE creative contract with your own model (agents: write the beats yourself — narration, shot-specs, motion notes, render modes, reference routing, emphasis words, recurring cast) and Genfire only renders; or pass just `topic` and Genfire writes the script. Async — returns a run in `processing`; poll `GET /v1/runs/{runId}`.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Body Fields

topic string required

What the explainer is about. Also used as the video title. Required even with a `script` (labels the run + video).

script object optional

Structured agent-authored script: { cast?: [{ name, description }] (≤3, UPPERCASE names, one fixed visual identity each), beats: [{ narration (spoken words, may carry ElevenLabs [audio tags]), visual (concrete storyboard shot-spec with quoted on-screen labels), motion? (camera/animation director note), render? ("anchored"|"direct"), continues? (bool — continue previous shot via last-frame chaining; prefer true on most beats so the film flows as continuous footage, breaking only at deliberate scene changes; never where a cast member enters/returns — their introduction needs an anchored shot), refs? ([1-based reference_images indices]), emphasis? (≤3 verbatim narration substrings for keyword captions), cast? ([declared names]) }] (3–100 beats). Bypasses EVERY internal LLM call; narration length sets the duration (target_duration_sec is ignored). Validation errors return 400 `invalid_script` with the exact fix.

custom_script string optional

Plain word-for-word narration (no beat structure). Genfire still storyboards the visuals for you. Ignored when `script` is present.

allow_duplicate boolean optional

Authored scripts (`script` / `custom_script`) that re-tell an episode this account already rendered are refused with 409 duplicate_script (the match is named). Pass true to render anyway.

Default: false

style_id string optional

Visual style preset from `GET /v1/explainers/styles` (41 looks: pixel-art, claymation, whiteboard-doodle…). Defaults to a 2D-illustration look.

aspect_ratio string optional

"16:9" (default) or "9:16".

Default: 16:9

target_duration_sec number optional

Target runtime in seconds (20–600). Ignored when `script` is present — the narration length sets the duration.

Default: 60

voice_id string optional

Narration voice id (ElevenLabs; renders on eleven_v4 with expressive [audio tags]). Defaults to a documentary narrator.

motion_level string optional

How many scenes render as real omni video clips: "full" (every scene, default) | "mixed" (every 5th) | "stills" (Ken-Burns only, cheapest). Draft-run lever for agents.

Default: full

continuity string optional

How animated scenes join: "seamless" (default — each clip seeds from the previous clip's last frame; continuous footage) | "cuts" (every scene is its own shot, hard cuts — "scene by scene"). Stills ignore it.

Default: seamless

fast_mode boolean optional

Fast mode: animated scenes render on MiniMax H3 Max Turbo (4 cr/s at 768P, 5–15s clips) instead of Omni Flash 1.1 (10 cr/s at 720p). Scenes that feature reference images or cast sheets stay on omni's reference tier (Turbo has none). Faster and cheaper per animated scene; clips carry no generated ambience.

Default: false

music object optional

Background music bed: { source: "none"|"preset"|"ai"|"library", preset_id?, prompt?, track_id? }.

caption_preset_id string optional

Captions are OPT-IN — omit for none. Any caption preset id from `GET /v1/faceless-reels/caption-presets` enables burned captions.

Default: none

caption_position string optional

Vertical caption placement: "top" | "middle" | "bottom" (default when captions are on).

caption_mode string optional

"full" burns the transcript; "keywords" pops only each beat's emphasis words at their spoken timestamps.

Default: full

caption_animation string optional

Override the caption animation (highlight | pop | typewriter | classic | background).

reference_images array optional

Up to 8 { url (https), label? (≤60 chars) } — products/characters/brands that must appear. Beats reference them via `refs` (1-based); those scenes render via omni reference-to-video. Put `refs: [1]` on the FIRST beat — the opening frame is generated as an image EDIT from the refs, locking the subject's real identity into the style anchor every later scene inherits.

team_id string optional

Bill this run to a WORKSPACE (team) credit pool instead of your balance (`GET /v1/teams`). Membership and a spending role are asserted before the run is created — 404 `invalid_team_id` when you are not a member, 403 `team_role_cannot_spend` when your role cannot spend. Requires the `teams:read` scope. A route that cannot bill a pool yet returns 400 `team_billing_unsupported` rather than quietly charging you. The run echoes it back as `team_id`.

project_id string optional

File this run's output into one of your PROJECTS (`GET /v1/projects`), exactly as if you had the project open in the studio. Edit access is checked before the run is created. Routes whose output is not an image, video or audio a project can hold return 400 `project_filing_unsupported`. The run echoes it back as `project_id`, and `GET /v1/runs?project_id=` lists everything made inside it.

quote_token string optional

A signed price quote from the matching estimate endpoint, binding this submit to the price you were shown. May also be sent as the `X-Genfire-Quote` header. The token carries the quoted inputs, so it is only spendable on the request it was issued for: 409 `quote_expired` past its 10-minute life, 409 `quote_mismatch` when the inputs or the price no longer match, 401 `quote_invalid` when the signature does not verify. Every 409 carries a FRESH quote in the problem body so you can resubmit in one step.

curl Example

curl https://api.genfire.ai/v1/explainers/generations \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "topic": "How quantum computers actually work",
  "script": {
    "cast": [
      {
        "name": "QUBIT",
        "description": "a glowing translucent blue sphere with orbiting light rings, softly pulsing"
      }
    ],
    "beats": [
      {
        "narration": "Your laptop thinks in bits — tiny switches that are either on, or off.",
        "visual": "Clean diagram: a row of light switches on a circuit board, each labeled \"0\" or \"1\", one flipping",
        "motion": "Slow push-in as one switch flips",
        "emphasis": [
          "bits"
        ]
      },
      {
        "narration": "[curious] A quantum computer thinks in qubits — and a qubit can be both at once.",
        "visual": "QUBIT hovering between two glowing labels \"0\" and \"1\", overlapping into a blended glow labeled \"BOTH\"",
        "motion": "QUBIT drifts between the labels, rings spinning up",
        "cast": [
          "QUBIT"
        ],
        "emphasis": [
          "qubits",
          "both at once"
        ]
      },
      {
        "narration": "That superposition lets it explore millions of answers at the same time.",
        "visual": "Wide shot: QUBIT at the center of a branching tree of thousands of faint answer-paths lighting up simultaneously, label \"1,000,000 paths\"",
        "motion": "Camera pulls back as the tree ignites outward",
        "cast": [
          "QUBIT"
        ],
        "emphasis": [
          "superposition"
        ]
      }
    ]
  },
  "aspect_ratio": "16:9",
  "style_id": "2d-illustrator",
  "caption_preset_id": "minimal",
  "caption_mode": "keywords"
}'

Response Example

{
  "id": "run_8f3c4a2b1d6e7f9a0b1c2d3e",
  "object": "run",
  "status": "processing",
  "capability": "explainer_generation",
  "endpoint": "explainers.generations.create",
  "model": "explainer.omni",
  "resource_id": "reel_abc123",
  "created_at": "2026-07-06T12:00:00.000Z"
}

Request Example Body

{
  "topic": "How quantum computers actually work",
  "script": {
    "cast": [
      {
        "name": "QUBIT",
        "description": "a glowing translucent blue sphere with orbiting light rings, softly pulsing"
      }
    ],
    "beats": [
      {
        "narration": "Your laptop thinks in bits — tiny switches that are either on, or off.",
        "visual": "Clean diagram: a row of light switches on a circuit board, each labeled \"0\" or \"1\", one flipping",
        "motion": "Slow push-in as one switch flips",
        "emphasis": [
          "bits"
        ]
      },
      {
        "narration": "[curious] A quantum computer thinks in qubits — and a qubit can be both at once.",
        "visual": "QUBIT hovering between two glowing labels \"0\" and \"1\", overlapping into a blended glow labeled \"BOTH\"",
        "motion": "QUBIT drifts between the labels, rings spinning up",
        "cast": [
          "QUBIT"
        ],
        "emphasis": [
          "qubits",
          "both at once"
        ]
      },
      {
        "narration": "That superposition lets it explore millions of answers at the same time.",
        "visual": "Wide shot: QUBIT at the center of a branching tree of thousands of faint answer-paths lighting up simultaneously, label \"1,000,000 paths\"",
        "motion": "Camera pulls back as the tree ignites outward",
        "cast": [
          "QUBIT"
        ],
        "emphasis": [
          "superposition"
        ]
      }
    ]
  },
  "aspect_ratio": "16:9",
  "style_id": "2d-illustrator",
  "caption_preset_id": "minimal",
  "caption_mode": "keywords"
}

Notes

  • Explainers render in minutes to tens of minutes (a 10-minute film ≈ 30 min). Poll `GET /v1/runs/{runId}` — in-flight runs carry a `progress` stage card; completed output is `{ reel_id, video_url, script, scenes: [{ index, video_url, poster_url, prompt, narration, duration_sec, animated }], duration_seconds }` (per-scene clips included).
  • Cost scales with duration — call `POST /v1/explainers/estimate-cost` first (same body; with a `script` the quote derives from the narration length).
  • Structured-script authoring rules: `emphasis` phrases must quote the beat's spoken narration VERBATIM; `refs` must point at supplied reference_images; `cast` names must be declared in script.cast; a beat's visual is a concrete storyboard shot (subjects + composition + quoted on-screen text), never a metaphor; `hook_text` (3-6 words) becomes the on-screen hook card.
  • WRITING FOR RETENTION (what the internal writer is held to — hold yourself to it): (1) Most viewers arrive muted: give `hook_text` (3-6 words, ≤32 chars) a muted viewer cannot scroll past — a claim, number, contradiction or open loop, not a title. (2) Beat 1 opens mid-action on the single most shocking concrete detail — never "Did you know", "Imagine", "What if", a greeting or a restatement — and its visual is a MOMENT OF CHANGE (something opening, falling, breaking, revealed), never a portrait. (3) Beat jobs by position: hook → promise (what they get, without giving it) → escalate → RE-HOOK at ~40% where retention sags (a reversal / "but then") → payoff, with a last sentence that earns a replay or a comment, never a sign-off. (4) One scene every ~4s for shorts; present tense; concrete names, numbers, sensory detail; every narration a complete sentence. (5) `emphasis` on most beats: 1-3 VERBATIM narration words that should pop on screen. (6) Facts formats: order by impact, keep the most jaw-dropping verified fact for last, no manufactured twists; never invent a specific you are not sure of.
  • The same `Idempotency-Key` returns the same run instead of generating (and billing) twice.
POST /explainers/estimate-cost
reels:read

Estimate explainer credit cost

Returns a per-config credit breakdown (frames + voiceover + music + video clips) for a prospective explainer, without generating anything. Accepts the same body as generate; with a `script` the duration derives from the narration length.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

target_duration_sec number optional

Target runtime in seconds (20–600). Ignored when `script` is present.

script object optional

Structured script (same shape as generate) — the quote derives from its narration length.

motion_level string optional

"full" | "mixed" | "stills" — how many scenes render as real video clips (the dominant cost lever).

Default: full

fast_mode boolean optional

Fast mode: animated scenes render on MiniMax H3 Max Turbo (4 cr/s at 768P, 5–15s clips) instead of Omni Flash 1.1 (10 cr/s at 720p). Scenes that feature reference images or cast sheets stay on omni's reference tier (Turbo has none). Faster and cheaper per animated scene; clips carry no generated ambience.

Default: false

music object optional

Music config; only `source: "ai"` adds a music fee.

voice_id string optional

Narration voice (a Seed Audio preset switches the TTS credit key).

aspect_ratio string optional

"16:9" | "9:16" (no cost impact today; accepted for parity).

curl Example

curl https://api.genfire.ai/v1/explainers/estimate-cost \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "target_duration_sec": 120,
  "motion_level": "full",
  "music": {
    "source": "preset"
  }
}'

Response Example

{
  "object": "explainer_cost_estimate",
  "effective_duration_sec": 120,
  "images": 42,
  "voiceover": 18,
  "music": 0,
  "videoClips": 220,
  "total": 280,
  "sceneCount": 20,
  "animatedScenes": 20,
  "quote_id": "quote_9f1c2ab34d5e6f70",
  "expires_at": "2026-09-12T18:24:00.000Z",
  "quote_token": "eyJxIjoi...fQ.f3Kb2p9w"
}

Request Example Body

{
  "target_duration_sec": 120,
  "motion_level": "full",
  "music": {
    "source": "preset"
  }
}
GET /explainers/styles
reels:read

List explainer visual styles

Lists the explainer visual style presets that can be passed as `style_id`.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/explainers/styles \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "pixel-art",
      "label": "Pixel Art",
      "preview_url": "https://genfire.ai/explainer-styles/pixel-art.jpg",
      "preview_video_url": "https://storage.googleapis.com/genfire-2.firebasestorage.app/style-previews/explainers/pixel-art.mp4"
    },
    {
      "id": "claymation",
      "label": "Claymation",
      "preview_url": "https://genfire.ai/explainer-styles/claymation.jpg",
      "preview_video_url": "https://storage.googleapis.com/genfire-2.firebasestorage.app/style-previews/explainers/claymation.mp4"
    }
  ]
}

Notes

  • `preview_url` is a still; `preview_video_url` is a short looping clip that may not exist for every style yet (treat a 404 as “no clip”, fall back to the still).

Reference

Music Videos

POST /music-videos/generations
reels:write

Generate a music video

Produces an auto-directed AI music video (9:16 or 16:9) from a song: song → beat/section analysis → style-locked anchor frame → per-scene MiniMax H3 Max Turbo clips cut to the music, with performance shots lip-synced to the song itself (H3 Max Lip Sync) → composed video with optional karaoke lyric captions. Bring your own track via `song_url` (with optional lyrics/sections/word timestamps), or pass an inline `song` prompt and Genfire generates the track first (ElevenLabs Music v2.5, billed as its own step). Async — returns a run in `processing`; poll `GET /v1/runs/{runId}`.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Body Fields

concept string required

The creative concept / narrative direction for the video (e.g. "a lonely astronaut drifting through neon ruins, longing for home"). Steers the shot-list and styling.

song_url string optional

https URL of the song to score the video to. Provide THIS or an inline `song` (not both). Optionally pair with `song_title`, `song_lyrics`, `song_sections`, `song_word_timestamps`.

song object optional

Inline song generation (instead of `song_url`): { prompt (what the track should sound like), duration_ms (10000–600000), instrumental? }. Genfire generates the track first via ElevenLabs Music v2.5 with word timestamps, bills it separately, then produces the video.

song_title string optional

Title for a bring-your-own `song_url` track (labels the output). Ignored with an inline `song`.

song_lyrics string optional

Full lyrics for a bring-your-own track — improves lyric-aware pacing and captions.

song_sections array optional

Optional pre-computed sections for a bring-your-own track: [{ label, start_sec, end_sec }]. Skips section analysis.

song_word_timestamps array optional

Optional word timings for a bring-your-own track: [{ word, start_sec, end_sec }]. Drive karaoke captions AND lip-synced singing shots — without timings (or `transcribe_lyrics`) performers are animated from the prompt instead.

transcribe_lyrics boolean optional

For a bring-your-own `song_url` only: transcribe the track to derive lyrics + word timestamps (unlocks lyric_captions and lyric-aware scenes). Runs as its own step, billed per second (Whisper). No-op when `song_word_timestamps` were supplied or an inline `song` is used (those get timings natively); an instrumental track proceeds without lyrics. Default false.

Default: false

style_preset_id string required

Visual style preset id from `GET /v1/music-videos/styles` (cinematic-performance, anime, neon-city…).

aspect_ratio string optional

"9:16" (default) or "16:9".

Default: 9:16

scene_density string optional

How many scenes the song is cut into: "low" (~10s/scene) | "medium" (~7s) | "high" (~5s). More scenes = more clips = higher cost.

Default: medium

lyric_captions boolean optional

Burn karaoke-style lyric captions (uses word timestamps from the song). Default false.

Default: false

reference_images array optional

Up to 8 { url (https), label? (≤60 chars) } — characters/products/looks that should appear and anchor the style.

team_id string optional

Bill this run to a WORKSPACE (team) credit pool instead of your balance (`GET /v1/teams`). Membership and a spending role are asserted before the run is created — 404 `invalid_team_id` when you are not a member, 403 `team_role_cannot_spend` when your role cannot spend. Requires the `teams:read` scope. A route that cannot bill a pool yet returns 400 `team_billing_unsupported` rather than quietly charging you. The run echoes it back as `team_id`.

project_id string optional

File this run's output into one of your PROJECTS (`GET /v1/projects`), exactly as if you had the project open in the studio. Edit access is checked before the run is created. Routes whose output is not an image, video or audio a project can hold return 400 `project_filing_unsupported`. The run echoes it back as `project_id`, and `GET /v1/runs?project_id=` lists everything made inside it.

quote_token string optional

A signed price quote from the matching estimate endpoint, binding this submit to the price you were shown. May also be sent as the `X-Genfire-Quote` header. The token carries the quoted inputs, so it is only spendable on the request it was issued for: 409 `quote_expired` past its 10-minute life, 409 `quote_mismatch` when the inputs or the price no longer match, 401 `quote_invalid` when the signature does not verify. Every 409 carries a FRESH quote in the problem body so you can resubmit in one step.

curl Example

curl https://api.genfire.ai/v1/music-videos/generations \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "concept": "a lonely astronaut drifting through neon-lit ruins, longing for home",
  "song": {
    "prompt": "melancholic synthwave, slow build, emotional female vocal",
    "duration_ms": 90000
  },
  "style_preset_id": "neon-city",
  "aspect_ratio": "9:16",
  "scene_density": "medium",
  "lyric_captions": true
}'

Response Example

{
  "id": "run_9a2b3c4d5e6f7a8b9c0d1e2f",
  "object": "run",
  "status": "processing",
  "capability": "music_video_generation",
  "endpoint": "music_videos.generations.create",
  "model": "music-video.omni",
  "resource_id": "reel_abc123",
  "created_at": "2026-07-21T12:00:00.000Z"
}

Request Example Body

{
  "concept": "a lonely astronaut drifting through neon-lit ruins, longing for home",
  "song": {
    "prompt": "melancholic synthwave, slow build, emotional female vocal",
    "duration_ms": 90000
  },
  "style_preset_id": "neon-city",
  "aspect_ratio": "9:16",
  "scene_density": "medium",
  "lyric_captions": true
}

Notes

  • Music videos render in minutes to tens of minutes. Poll `GET /v1/runs/{runId}` — in-flight runs carry a `progress` stage card; completed output is `{ reel_id, video_url, script, scenes, duration_seconds }`.
  • Pass EXACTLY ONE of `song_url` or inline `song`. With an inline `song`, the track is generated and billed BEFORE the video-production stage (a separate ElevenLabs Music v2.5 charge). The song duration is always probed server-side and capped (default 300s).
  • Cost scales with song length and scene_density — call `POST /v1/music-videos/estimate-cost` first (that quote covers the video-production stage only, not the song).
  • The same `Idempotency-Key` returns the same run instead of generating (and billing) twice.
POST /music-videos/estimate-cost
reels:read

Estimate music-video credit cost

Returns a per-config credit breakdown (style anchor frame + per-scene AI clips) for the VIDEO-PRODUCTION stage of a prospective music video, without generating anything. The song's own cost is not included (the song is produced/uploaded before this stage as its own billed step).

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

song_duration_sec number required

Length of the song in seconds (the dominant driver of scene count).

aspect_ratio string optional

"9:16" (default) or "16:9".

Default: 9:16

scene_density string optional

"low" | "medium" | "high" — scenes per second of song (the main cost lever).

Default: medium

lyric_captions boolean optional

Whether karaoke lyric captions are burned in (no material cost impact today; accepted for parity).

Default: false

curl Example

curl https://api.genfire.ai/v1/music-videos/estimate-cost \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "song_duration_sec": 90,
  "aspect_ratio": "9:16",
  "scene_density": "medium"
}'

Response Example

{
  "object": "music_video_cost_estimate",
  "totalCredits": 220,
  "sceneCount": 13,
  "breakdown": [
    {
      "label": "Style anchor image (1× gpt-image-2)",
      "credits": 20
    },
    {
      "label": "AI clips (9 of 13 scenes; ~30% chorus-reuse assumed for songs over 90s, 7s each)",
      "credits": 200
    }
  ],
  "quote_id": "quote_9f1c2ab34d5e6f70",
  "expires_at": "2026-09-12T18:24:00.000Z",
  "quote_token": "eyJxIjoi...fQ.f3Kb2p9w"
}

Request Example Body

{
  "song_duration_sec": 90,
  "aspect_ratio": "9:16",
  "scene_density": "medium"
}
GET /music-videos/styles
reels:read

List music-video visual styles

Lists the music-video visual style presets that can be passed as `style_preset_id`.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/music-videos/styles \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Example

{
  "object": "list",
  "data": [
    {
      "id": "cinematic-performance",
      "name": "Cinematic Performance",
      "description": "Filmic, anamorphic performance footage — moody key light, shallow depth of field, real-camera energy.",
      "preview_url": "https://firebasestorage.googleapis.com/v0/b/genfire-2.firebasestorage.app/o/apps%2Fmusic-video-styles%2Fcinematic-performance.webp?alt=media",
      "preview_video_url": "https://storage.googleapis.com/genfire-2.firebasestorage.app/style-previews/music-videos/cinematic-performance.mp4"
    },
    {
      "id": "anime",
      "name": "Anime",
      "description": "Vibrant modern anime — expressive characters, dynamic skies, sakuga-grade motion.",
      "preview_url": "https://firebasestorage.googleapis.com/v0/b/genfire-2.firebasestorage.app/o/apps%2Fmusic-video-styles%2Fanime.webp?alt=media",
      "preview_video_url": "https://storage.googleapis.com/genfire-2.firebasestorage.app/style-previews/music-videos/anime.mp4"
    }
  ]
}

Notes

  • `preview_url` is a still; `preview_video_url` is a short looping clip that may not exist for every style yet (treat a 404 as “no clip”, fall back to the still).

Reference

Coloring Books

POST /coloring-books/generations
images:write

Draw a coloring book

Draw a complete black-and-white coloring book. NOT a picture book: there is no story and no words on the pages — each page is a separate line drawing, and only the cover is in colour. Two ways in. Author the book yourself with `plan` (a page list, each with a one-sentence `subject`) and it is drawn exactly as written, with no server planning pass. Or send a `theme` (or `idea`) and the server plans the page list for you. Authoring is strongly preferred: the single most common failure is a repetitive book, and repeats are visible in a plan before any credits are spent. Every page is a paid image and a full book runs long, so call `POST /v1/coloring-books/estimate-cost` first — the same parser backs both routes, so the estimate is priced against exactly what you are about to send, and reading the subjects back is the cheapest way to catch a dull book. Async: returns a run in `processing`. Poll `GET /v1/runs/{runId}`, then read the book with `GET /v1/coloring-books/{id}`.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Body Fields

plan object optional

YOUR authored book, drawn as written: `{ title?, theme?, subtitle?, cast?, pages: [{ subject, composition?, caption?, cast? }], cover_visual, back_cover_blurb? }`. 8-120 pages; `subject` is one concrete sentence saying what is drawn and what it is doing, never mentioning colour, ink or outlines. Repeated subjects are dropped and reported in `plan.warnings`. Supplying this skips the planner entirely.

theme string optional

One line describing what every page shows, for the server to plan from (max 600 chars). Provide exactly one of `plan`, `theme` or `idea`.

idea string optional

Same role as `theme` (max 600 chars).

title string optional

Book title, max 120 chars.

pages number optional

How many drawings, 8-120. Ignored when `plan` is present — the plan's own page count wins.

Default: by complexity: toddler 30, kids 40, tween 50, adult 60

complexity string optional

Detail per drawing: "toddler", "kids", "tween" or "adult".

Default: kids

line_weight string optional

"bold", "medium" or "fine".

Default: the complexity's own — toddler/kids bold, tween/adult medium

border string optional

"none", "thin" or "decorative" frame around each drawing.

Default: none

style_id string optional

Subject world: mandala, zentangle, kawaii, botanical, animals, dinosaurs, ocean, fantasy, vehicles, cityscape, folk, stained-glass, geometric, holiday, alphabet, patterns. A style names WHAT is drawn; the medium is always black ink on white paper.

Default: animals

format_id string optional

Trim: kdp-8.5x11, kdp-8x10, kdp-8.5x8.5, a4, kdp-6x9, digital-3-4, digital-1-1.

Default: kdp-8.5x11

quality string optional

"low" (cheap draft) or "medium" (several times the price).

Default: low

blank_backs boolean optional

Interleave a blank page behind each drawing so crayon does not bleed through. DOUBLES the printed interior page count. Forced false on digital formats.

Default: true on print formats

bleed boolean optional

Full-bleed art. Print formats only; forced false on digital.

Default: false

captions boolean optional

Typeset the page subject as a caption under each drawing.

Default: false

cast object[] optional

Up to 4 recurring characters, `{ name, description }` — character coloring books only.

team_id string optional

Bill a workspace (team) credit pool. Requires the `teams:read` scope and a spending role; the affordability pre-check reads that pool instead of the personal balance.

curl Example

curl https://api.genfire.ai/v1/coloring-books/generations \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "title": "Deep Sea Friends",
  "complexity": "kids",
  "style_id": "ocean",
  "format_id": "kdp-8.5x11",
  "plan": {
    "title": "Deep Sea Friends",
    "cover_visual": "A smiling octopus waving from a coral arch",
    "pages": [
      {
        "subject": "A curious octopus peeking out of a sunken teapot"
      },
      {
        "subject": "A sea turtle gliding over a ridge of branching coral"
      }
    ]
  }
}'

Response Example

{
  "id": "run_cb_7a1b2c3d",
  "object": "run",
  "status": "processing",
  "capability": "coloring_book_generation",
  "endpoint": "coloring_books.generations.create",
  "model": "openai/gpt-image-2",
  "output": {
    "book_id": "cbook_4f8e",
    "url": "https://genfire.ai/dashboard/coloring-books/cbook_4f8e"
  },
  "resource_id": "cbook_4f8e",
  "created_at": "2026-09-12T10:00:00.000Z"
}

Request Example Body

{
  "title": "Deep Sea Friends",
  "complexity": "kids",
  "style_id": "ocean",
  "format_id": "kdp-8.5x11",
  "plan": {
    "title": "Deep Sea Friends",
    "cover_visual": "A smiling octopus waving from a coral arch",
    "pages": [
      {
        "subject": "A curious octopus peeking out of a sunken teapot"
      },
      {
        "subject": "A sea turtle gliding over a ridge of branching coral"
      }
    ]
  }
}

Notes

  • The whole book is afford-checked BEFORE anything is created: an unaffordable request is 402 `insufficient_credits` naming the estimate and the balance, with no book document left behind.
  • Cost = 1 anchor + up to 4 cast sheets + one image per page + 2 cover images, each at the chosen `quality`. Price it with `POST /v1/coloring-books/estimate-cost`, which runs the identical parser.
  • `project_id` is NOT accepted — naming one is 400 `project_filing_unsupported`. Read the finished book with `GET /v1/coloring-books/{id}` and export it with `POST /v1/coloring-books/{id}/export`.
  • A `quote_token` from the estimate route is NOT verified here yet; the estimate and the charge still agree because both run the same estimator.

Reference

Apps

POST /apps/deployments
images:write

Deploy an app or website you wrote

YOU write the app; Genfire hosts it. There is no server-side codegen on this route — it takes the finished document and ships it, so empty or placeholder `html` deploys a broken page. Send ONE complete self-contained HTML document: inline CSS and JS, CDN libraries allowed, and it must boot instantly. It is smoke-tested in a real headless browser BEFORE any credits move, so a page that throws on load comes back 422 `boot_failed` with the actual boot error and costs nothing. Share only the returned `live_url` — the branded genfire.ai page. Never the storage URL, and never a URL you assembled yourself. Redeploy to the same URL by passing a previous `app_id`. Synchronous: the run comes back already `completed`.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Required

Body Fields

html string required

The complete single-file document. Must start with `<!DOCTYPE html>` or `<html>` and end with `</html>`; 200 bytes to 1,500,000 bytes.

title string optional

Name in the user's library, trimmed to 80 chars.

Default: the document's own <title>, else the first 60 chars of `brief`, else "Deployed app"

brief string optional

What the app is, max 2000 chars. Stored as the prompt and used to generate the thumbnail.

kind string optional

"app" or "website". Anything else is treated as "app".

Default: app

app_id string optional

Redeploy an app you already own, in place at the same `live_url`. Skips thumbnail generation. An app you do not own is 404 `app_not_found`.

team_id string optional

Bill a workspace (team) credit pool instead of the personal balance. Requires the `teams:read` scope and a spending role.

curl Example

curl https://api.genfire.ai/v1/apps/deployments \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: unique_request_key" \
  -H "Content-Type: application/json" \
  -d '{
  "title": "Pricing calculator",
  "kind": "website",
  "brief": "A single-page pricing calculator for a SaaS plan",
  "html": "<!DOCTYPE html><html>…</html>"
}'

Response Example

{
  "id": "run_app_2b7c1d9e",
  "object": "run",
  "status": "completed",
  "capability": "app_generation",
  "endpoint": "apps.deployments.create",
  "model": null,
  "output": {
    "app_id": "app_5c3d1e2f",
    "live_url": "https://genfire.ai/apps/app_5c3d1e2f/view",
    "thumbnail_url": "https://cdn.genfire.ai/apps/app_5c3d1e2f/thumb.png",
    "title": "Pricing calculator",
    "kind": "website"
  },
  "resource_id": "app_5c3d1e2f",
  "created_at": "2026-09-12T10:00:00.000Z",
  "completed_at": "2026-09-12T10:00:09.000Z"
}

Request Example Body

{
  "title": "Pricing calculator",
  "kind": "website",
  "brief": "A single-page pricing calculator for a SaaS plan",
  "html": "<!DOCTYPE html><html>…</html>"
}

Notes

  • The boot check runs BEFORE billing: a 422 `boot_failed` costs nothing. Fix the reported error and deploy again with a NEW `Idempotency-Key` — replaying the old one returns the failed run.
  • Flat price (5 credits by default) regardless of document size.
  • `project_id` is NOT accepted — an app is not media a project can hold, so naming one is 400 `project_filing_unsupported`.
  • To list a finished app in the public gallery, call `POST /v1/apps/{appId}/publish` afterwards.

Reference

Ads

POST /ads/analyze
workflows:write

Reverse-engineer a competitor ad into a reusable format

Take one ad from `GET /v1/ads/search` and extract the FORMAT behind it — hook type, beat structure, angle, promise, mechanism, proof, tone and CTA style — into your research library. The ad's video is mirrored to your own storage so the research survives the ad library rotating it out. What comes back is a STRUCTURE, never assets or wording: the returned `research_id` can be passed to the `ugc_ad_video` workflow as `reference_ad_research_id` to produce an ad in that proven shape for YOUR product. Nothing from the source ad is copied. Free, and synchronous. Re-read a stored analysis later with `GET /v1/ads/research/{id}`.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

page_id string required

The advertiser page id from a `GET /v1/ads/search` result.

ad_id string required

The ad to analyse, from that page's current results. An ad that is no longer in the library is 404 `ad_not_found`.

team_id string optional

Stamp the research document with this workspace so teammates can read and clone it. Requires the `teams:read` scope.

curl Example

curl https://api.genfire.ai/v1/ads/analyze \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "page_id": "102938475610111",
  "ad_id": "24680135791113"
}'

Response Example

{
  "research_id": "adres_6f1e2d3c",
  "ad": {
    "ad_id": "24680135791113",
    "page_id": "102938475610111",
    "page_name": "Acme Skincare",
    "started_at": "2026-06-02",
    "is_active": true,
    "days_running": 102,
    "text": "The 30-second step that changed my skin",
    "cta": "Shop Now",
    "video_url": "https://cdn.genfire.ai/ad-research/adres_6f1e2d3c/source.mp4",
    "image_urls": [],
    "platforms": [
      "facebook",
      "instagram"
    ]
  },
  "analysis": {
    "hook_type": "problem-agitate",
    "hook_text": "The 30-second step that changed my skin",
    "structure": [
      "hook",
      "problem",
      "mechanism",
      "proof",
      "offer",
      "cta"
    ],
    "angle": "time-poor routine",
    "promise": "visible change in two weeks",
    "mechanism": "encapsulated retinol",
    "proof": "before/after plus a dermatologist quote",
    "tone": "confiding",
    "cta_style": "soft-direct",
    "why_it_works": "The hook names the objection (time) before the benefit."
  },
  "mirrored_video_url": "https://cdn.genfire.ai/ad-research/adres_6f1e2d3c/source.mp4",
  "note": null
}

Request Example Body

{
  "page_id": "102938475610111",
  "ad_id": "24680135791113"
}

Notes

  • Free: no credits are charged, and no `api_runs` row is created — there is nothing to poll.
  • `days_running` on the returned ad is the performance proxy worth reading: 45+ days generally means a proven winner.
  • `project_id` is NOT accepted — an analysis is research, not media, so naming one is 400 `project_filing_unsupported` with exactly that reason.
  • Pass the `research_id` to `POST /v1/workflows/ugc_ad_video/runs` as `reference_ad_research_id` to generate in this format.

Reference

Media

POST /media/frames
videos:write

Extract video frames and a contact sheet

Decodes a video URL or completed owned run into JPEG frames. Defaults to six evenly spaced samples and a labeled contact sheet. Input transfer is limited to 100 MiB. Owned-media host restrictions apply. No generation is performed.

Auth: Bearer API key or OAuth access token
Idempotency-Key: Not required

Body Fields

No body fields for this endpoint.

curl Example

curl https://api.genfire.ai/v1/media/frames \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "video_url": "run_5f3c9a21c0d84e7e8b2a6d1f4c07b935",
  "times": [
    0,
    2.5,
    5
  ],
  "width": 640
}'

Response Example

{
  "object": "video_frames",
  "duration": 12.48,
  "frames": [
    {
      "time": 0,
      "url": "https://firebasestorage.googleapis.com/v0/b/genfire-2.firebasestorage.app/o/media-derivatives%2Fframes%2Fframe-0.jpg?alt=media&token=EXAMPLE",
      "width": 640,
      "height": 360
    },
    {
      "time": 6.24,
      "url": "https://firebasestorage.googleapis.com/v0/b/genfire-2.firebasestorage.app/o/media-derivatives%2Fframes%2Fframe-1.jpg?alt=media&token=EXAMPLE",
      "width": 640,
      "height": 360
    }
  ],
  "contact_sheet": {
    "url": "https://firebasestorage.googleapis.com/v0/b/genfire-2.firebasestorage.app/o/media-derivatives%2Fframes%2Fcontact-sheet.jpg?alt=media&token=EXAMPLE",
    "width": 1280,
    "height": 720
  }
}

Request Example Body

{
  "video_url": "run_5f3c9a21c0d84e7e8b2a6d1f4c07b935",
  "times": [
    0,
    2.5,
    5
  ],
  "width": 640
}
Copied to clipboard