# ArtCraft Omni API > # πŸ“š Full API reference: **** > > **Every ArtCraft API endpoint is documented there.** This guide covers only the Omni API video > endpoints β€” for everything else, start at the link above. The **Omni API** is the API-key–authenticated surface for ArtCraft generation. It mirrors the in-app "Omni Gen" endpoints but is designed for programmatic use: you authenticate with an API key in the `Authorization` header instead of a browser session cookie. This guide covers **video generation from URL inputs** β€” passing image, video, and audio reference URLs directly. The server downloads each URL, stores it as a media file on your account, and then runs generation against it. You never have to upload files yourself. > All examples send **no cookies**. The Omni API is authenticated purely by your API key. If you > send session cookies they are ignored. --- ## Base URLs | Environment | Base URL | | ------------- | ------------------------------ | | Development | `http://localhost:12345` | | Production | `https://api.storyteller.ai` | All paths below are relative to the base URL (e.g. `POST /v1/omni_api/generate/video`). The examples in this guide use the **development** base URL. To run against production, swap `http://localhost:12345` for `https://api.storyteller.ai`. Note: Our APIs are hosted on "`api.storyteller.ai`", not "`api.getartcraft.com`". This is not a typo. Our Cloudflare CDN URLs are `cdn-2.fakeyou.com`. --- ## Getting an API key Create and manage API keys from the ArtCraft web app: 1. Go to **** 2. Click **Account β†’ Settings β†’ API Keys** (your account must have API keys enabled from our staff) 3. Create a key and copy it. The secret is shown **once** β€” store it somewhere safe. > **Don't see the API Keys section?** API access is gated per account. Contact the Artcraft team > to enable it. ### What an API key looks like - Prefix: `artcraft_api_` - Followed by 40 random lowercase Crockford-base32 characters (`0-9 a-z`, excluding `i l o u`) - Total length: **53 characters**, e.g. `artcraft_api_3k7q9w0xv2hs5n8m4d6r1t8y0p2s4f6h8j0k2m4n6` Treat the key like a password: never commit it to source control or paste it into logs. --- ## Authentication Send the secret key in the `Authorization` header. Three forms are accepted: ``` Authorization: Bearer artcraft_api_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Authorization: Key artcraft_api_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Authorization: artcraft_api_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ``` `Bearer` is recommended. **Do not send cookies.** --- ## Video generation β€” `POST /v1/omni_api/generate/video` Generates a video and returns an inference job token you can poll for results. ### Request body | Field | Type | Notes | | ---------------------------- | --------------- | ----- | | `model` | string | Generation model, e.g. `seedance_2p0`. **Required.** | | `prompt` | string | Text prompt. | | `duration_seconds` | integer | Length of the output video, e.g. `6`. | | `aspect_ratio` | string | e.g. `wide_sixteen_by_nine`, `tall_nine_by_sixteen`, `square`. | | `idempotency_token` | string (UUID) | **Required.** A fresh UUID per request; prevents accidental duplicates. | | `negative_prompt` | string | Optional. | | `resolution` | string | Optional, e.g. `seven_twenty_p`, `ten_eighty_p`. | | `generate_audio` | boolean | Optional. | | **URL inputs** (this guide) | | | | `start_frame_image_url` | string (URL) | Starting keyframe image. | | `end_frame_image_url` | string (URL) | Ending keyframe image. | | `reference_image_urls` | array of URL | Reference images. | | `reference_video_urls` | array of URL | Reference videos (`mp4`). | | `reference_audio_urls` | array of URL | Reference audio (`wav`, `mp3`, `ogg`, …). | | **Media-token inputs** | | | | `start_frame_image_media_token`, `end_frame_image_media_token`, `reference_image_media_tokens`, `reference_video_media_tokens`, `reference_audio_media_tokens` | token(s) | Use these instead if you've already uploaded media. | #### Rules for URL inputs - Each URL field and its media-token counterpart are **mutually exclusive**. Sending both `reference_image_urls` and `reference_image_media_tokens`, for example, returns: `Either reference_image_media_tokens or reference_image_urls must be set, not both`. - Every URL must start with `http://` or `https://`, otherwise: `URL must start with http:// or https://, bad URL: `. - Redirects are followed (up to 10), so CDN/redirecting URLs work. - The file type is detected from the downloaded bytes. Images must be a real image (`jpeg`/`png`/`gif`/`webp`), reference videos must be `mp4`, reference audio must be a supported audio type (`wav`, `mp3`, `aac`, `ogg`, `flac`, …). ### Response ```json { "success": true, "inference_job_token": "jinf_xxxxxxxxxxxxxxxxxxxxxxxxx", "all_job_tokens": ["jinf_xxxxxxxxxxxxxxxxxxxxxxxxx"] } ``` Use `inference_job_token` to poll for the finished video. `all_job_tokens` lists every job created (batch requests can create more than one). --- ## Examples The examples reuse these public sample URLs: - Images (Wikimedia Commons): an Atlantic puffin, the Matterhorn, a toco toucan - Video: a short sample `mp4` - Audio: a short sample `wav` Replace `YOUR_API_KEY` with your `artcraft_api_…` secret, and generate a fresh `idempotency_token` (UUID) for every request. --- ### Example 1 β€” Starting-frame Seedance (`start_frame_image_url`) Animate outward from a single starting image. #### bash ```bash curl -s -X POST http://localhost:12345/v1/omni_api/generate/video \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "seedance_2p0", "prompt": "An Atlantic puffin stands on a windy cliff edge and turns its head as morning light catches its colorful beak.", "duration_seconds": 6, "aspect_ratio": "wide_sixteen_by_nine", "idempotency_token": "'"$(uuidgen)"'", "start_frame_image_url": "https://commons.wikimedia.org/wiki/Special:FilePath/Puffin_%28Fratercula_arctica%29.jpg?width=900" }' ``` #### python ```python import uuid import requests BASE_URL = "http://localhost:12345" # production: https://api.storyteller.ai API_KEY = "YOUR_API_KEY" resp = requests.post( f"{BASE_URL}/v1/omni_api/generate/video", headers={"Authorization": f"Bearer {API_KEY}"}, json={ "model": "seedance_2p0", "prompt": "An Atlantic puffin stands on a windy cliff edge and turns its head as morning light catches its colorful beak.", "duration_seconds": 6, "aspect_ratio": "wide_sixteen_by_nine", "idempotency_token": str(uuid.uuid4()), "start_frame_image_url": "https://commons.wikimedia.org/wiki/Special:FilePath/Puffin_%28Fratercula_arctica%29.jpg?width=900", }, timeout=240, ) resp.raise_for_status() print(resp.json()) ``` #### javascript ```javascript import { randomUUID } from "node:crypto"; const BASE_URL = "http://localhost:12345"; // production: https://api.storyteller.ai const API_KEY = "YOUR_API_KEY"; const resp = await fetch(`${BASE_URL}/v1/omni_api/generate/video`, { method: "POST", headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ model: "seedance_2p0", prompt: "An Atlantic puffin stands on a windy cliff edge and turns its head as morning light catches its colorful beak.", duration_seconds: 6, aspect_ratio: "wide_sixteen_by_nine", idempotency_token: randomUUID(), start_frame_image_url: "https://commons.wikimedia.org/wiki/Special:FilePath/Puffin_%28Fratercula_arctica%29.jpg?width=900", }), }); console.log(resp.status, await resp.json()); ``` --- ### Example 2 β€” Image-reference Seedance (`reference_image_urls`) Guide the generation with one or more reference images. #### bash ```bash curl -s -X POST http://localhost:12345/v1/omni_api/generate/video \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "seedance_2p0", "prompt": "A whimsical journey: an Atlantic puffin perches on a cliff beneath the snow-capped Matterhorn while a brilliant toco toucan watches from a misty rainforest canopy.", "duration_seconds": 6, "aspect_ratio": "wide_sixteen_by_nine", "idempotency_token": "'"$(uuidgen)"'", "reference_image_urls": [ "https://commons.wikimedia.org/wiki/Special:FilePath/Puffin_%28Fratercula_arctica%29.jpg?width=900", "https://commons.wikimedia.org/wiki/Special:FilePath/Matterhorn_from_Domh%C3%BCtte_-_2.jpg?width=900", "https://commons.wikimedia.org/wiki/Special:FilePath/Toco_toucan.jpg?width=900" ] }' ``` #### python ```python import uuid import requests BASE_URL = "http://localhost:12345" # production: https://api.storyteller.ai API_KEY = "YOUR_API_KEY" resp = requests.post( f"{BASE_URL}/v1/omni_api/generate/video", headers={"Authorization": f"Bearer {API_KEY}"}, json={ "model": "seedance_2p0", "prompt": "A whimsical journey: an Atlantic puffin perches on a cliff beneath the snow-capped Matterhorn while a brilliant toco toucan watches from a misty rainforest canopy.", "duration_seconds": 6, "aspect_ratio": "wide_sixteen_by_nine", "idempotency_token": str(uuid.uuid4()), "reference_image_urls": [ "https://commons.wikimedia.org/wiki/Special:FilePath/Puffin_%28Fratercula_arctica%29.jpg?width=900", "https://commons.wikimedia.org/wiki/Special:FilePath/Matterhorn_from_Domh%C3%BCtte_-_2.jpg?width=900", "https://commons.wikimedia.org/wiki/Special:FilePath/Toco_toucan.jpg?width=900", ], }, timeout=240, ) resp.raise_for_status() print(resp.json()) ``` #### javascript ```javascript import { randomUUID } from "node:crypto"; const BASE_URL = "http://localhost:12345"; // production: https://api.storyteller.ai const API_KEY = "YOUR_API_KEY"; const resp = await fetch(`${BASE_URL}/v1/omni_api/generate/video`, { method: "POST", headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ model: "seedance_2p0", prompt: "A whimsical journey: an Atlantic puffin perches on a cliff beneath the snow-capped Matterhorn while a brilliant toco toucan watches from a misty rainforest canopy.", duration_seconds: 6, aspect_ratio: "wide_sixteen_by_nine", idempotency_token: randomUUID(), reference_image_urls: [ "https://commons.wikimedia.org/wiki/Special:FilePath/Puffin_%28Fratercula_arctica%29.jpg?width=900", "https://commons.wikimedia.org/wiki/Special:FilePath/Matterhorn_from_Domh%C3%BCtte_-_2.jpg?width=900", "https://commons.wikimedia.org/wiki/Special:FilePath/Toco_toucan.jpg?width=900", ], }), }); console.log(resp.status, await resp.json()); ``` --- ### Example 3 β€” Image + video reference Seedance (`reference_image_urls` + `reference_video_urls`) Combine reference images with a reference video (and, optionally, reference audio). This is the fullest URL-driven request. #### bash ```bash curl -s -X POST http://localhost:12345/v1/omni_api/generate/video \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "seedance_2p0", "prompt": "An Atlantic puffin stands on a windy cliff edge; the scene sweeps up to the snow-capped Matterhorn at sunrise before settling on a brilliant toco toucan in a misty rainforest canopy. The camera glides with the motion of the reference video, set to the mood of the reference audio.", "duration_seconds": 6, "aspect_ratio": "wide_sixteen_by_nine", "idempotency_token": "'"$(uuidgen)"'", "reference_image_urls": [ "https://commons.wikimedia.org/wiki/Special:FilePath/Puffin_%28Fratercula_arctica%29.jpg?width=900", "https://commons.wikimedia.org/wiki/Special:FilePath/Matterhorn_from_Domh%C3%BCtte_-_2.jpg?width=900", "https://commons.wikimedia.org/wiki/Special:FilePath/Toco_toucan.jpg?width=900" ], "reference_video_urls": ["https://download.samplelib.com/mp4/sample-5s.mp4"], "reference_audio_urls": ["https://download.samplelib.com/wav/sample-6s.wav"] }' ``` #### python ```python import uuid import requests BASE_URL = "http://localhost:12345" # production: https://api.storyteller.ai API_KEY = "YOUR_API_KEY" resp = requests.post( f"{BASE_URL}/v1/omni_api/generate/video", headers={"Authorization": f"Bearer {API_KEY}"}, json={ "model": "seedance_2p0", "prompt": "An Atlantic puffin stands on a windy cliff edge; the scene sweeps up to the snow-capped Matterhorn at sunrise before settling on a brilliant toco toucan in a misty rainforest canopy. The camera glides with the motion of the reference video, set to the mood of the reference audio.", "duration_seconds": 6, "aspect_ratio": "wide_sixteen_by_nine", "idempotency_token": str(uuid.uuid4()), "reference_image_urls": [ "https://commons.wikimedia.org/wiki/Special:FilePath/Puffin_%28Fratercula_arctica%29.jpg?width=900", "https://commons.wikimedia.org/wiki/Special:FilePath/Matterhorn_from_Domh%C3%BCtte_-_2.jpg?width=900", "https://commons.wikimedia.org/wiki/Special:FilePath/Toco_toucan.jpg?width=900", ], "reference_video_urls": ["https://download.samplelib.com/mp4/sample-5s.mp4"], "reference_audio_urls": ["https://download.samplelib.com/wav/sample-6s.wav"], }, timeout=240, ) resp.raise_for_status() print(resp.json()) ``` #### javascript ```javascript import { randomUUID } from "node:crypto"; const BASE_URL = "http://localhost:12345"; // production: https://api.storyteller.ai const API_KEY = "YOUR_API_KEY"; const resp = await fetch(`${BASE_URL}/v1/omni_api/generate/video`, { method: "POST", headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ model: "seedance_2p0", prompt: "An Atlantic puffin stands on a windy cliff edge; the scene sweeps up to the snow-capped Matterhorn at sunrise before settling on a brilliant toco toucan in a misty rainforest canopy. The camera glides with the motion of the reference video, set to the mood of the reference audio.", duration_seconds: 6, aspect_ratio: "wide_sixteen_by_nine", idempotency_token: randomUUID(), reference_image_urls: [ "https://commons.wikimedia.org/wiki/Special:FilePath/Puffin_%28Fratercula_arctica%29.jpg?width=900", "https://commons.wikimedia.org/wiki/Special:FilePath/Matterhorn_from_Domh%C3%BCtte_-_2.jpg?width=900", "https://commons.wikimedia.org/wiki/Special:FilePath/Toco_toucan.jpg?width=900", ], reference_video_urls: ["https://download.samplelib.com/mp4/sample-5s.mp4"], reference_audio_urls: ["https://download.samplelib.com/wav/sample-6s.wav"], }), }); console.log(resp.status, await resp.json()); ``` --- ## Checking job status Generation is asynchronous. The generate call returns an `inference_job_token`; poll the job-status endpoint until the job reaches a terminal state, then read the result URL. ### Endpoints - **Single job:** `GET /v1/omni_api/job_status/job/{token}` - **Batch (many jobs):** `GET /v1/omni_api/job_status/batch?tokens=jinf_aaa&tokens=jinf_bbb` Both require the same `Authorization: Bearer …` header (no cookies). ### Response shape ```json { "success": true, "state": { "job_token": "jinf_xxxxxxxxxxxxxxxxxxxxxxxxx", "request": { "inference_category": "video_generation", "maybe_prompt_token": "prompt_xxxxxxxxxxxxxxxxxxxxxxxxx", "maybe_model_type": "seedance_2p0", "maybe_model_token": null }, "status": { "status": "pending", "maybe_first_started_at": null, "maybe_failure_category": null, "progress_percentage": 0 }, "maybe_result": null, "created_at": "2026-06-24T04:06:14Z", "updated_at": "2026-06-24T04:06:14Z" } } ``` - `state.status.status` β€” the job state. Keep polling while it's `pending`, `started`, or `attempt_failed`. **Terminal** states are `complete_success`, `complete_failure`, `dead`, `cancelled_by_user`, and `cancelled_by_system`. - `state.status.progress_percentage` β€” an integer 0–100. - `state.maybe_result` β€” `null` until the job succeeds. On `complete_success` it contains the finished media; the playable/downloadable URL is **`state.maybe_result.media_links.cdn_url`**. For video, `media_links.maybe_video_previews` also carries still/animated preview URLs. The batch endpoint returns `{ "success": true, "job_states": [ … ] }` where each entry is the same `state` payload as above. ### Where the result video / image lives When `state.status.status` is `complete_success`, the finished media is under `state.maybe_result`: - **`state.maybe_result.media_links.cdn_url`** β€” the primary, playable/downloadable URL of the finished asset. **This is the field you want.** For a video job it's the `.mp4`; for an image job it's the image file (`.jpg` / `.png` / etc.). - `state.maybe_result.entity_token` β€” the `m_…` media-file token for the result. - `state.maybe_result.media_links.maybe_video_previews` β€” **video jobs only**. Contains a `still` (poster `.jpg`) and an `animated` (`.gif`) preview URL, plus `{WIDTH}` thumbnail templates. This is `null` for image jobs. - `state.maybe_result.media_links.maybe_thumbnail_template` β€” **image jobs only**. A URL template; replace `{WIDTH}` with a pixel width to fetch a resized thumbnail. `null` for video jobs. **Example β€” completed video job:** ```json { "success": true, "state": { "job_token": "jinf_9fcn2qv4dv3vvbrrxbnmfgen06v", "request": { "inference_category": "video_generation", "maybe_prompt_token": "prompt_q53tp4rh97qh8q159p21tazzt", "maybe_model_type": "seedance_2p0", "maybe_model_token": null }, "status": { "status": "complete_success", "maybe_first_started_at": "2026-06-24T04:07:11Z", "maybe_failure_category": null, "progress_percentage": 100 }, "maybe_result": { "entity_type": "media_file", "entity_token": "m_ss682134tx19yhqs5y0nbnd0bd59a6", "media_links": { "cdn_url": "https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/media/8/p/n/0/4/8pn04v7q3vfhf60wy173f6awcwkre1qj/artcraft_8pn04v7q3vfhf60wy173f6awcwkre1qj.mp4", "maybe_thumbnail_template": null, "maybe_video_previews": { "still": "https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/media/8/p/n/0/4/8pn04v7q3vfhf60wy173f6awcwkre1qj/artcraft_8pn04v7q3vfhf60wy173f6awcwkre1qj.mp4-thumb.jpg", "animated": "https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/media/8/p/n/0/4/8pn04v7q3vfhf60wy173f6awcwkre1qj/artcraft_8pn04v7q3vfhf60wy173f6awcwkre1qj.mp4-thumb.gif", "still_thumbnail_template": "https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/media/8/p/n/0/4/8pn04v7q3vfhf60wy173f6awcwkre1qj/artcraft_8pn04v7q3vfhf60wy173f6awcwkre1qj.mp4-thumb-{WIDTH}.jpg", "animated_thumbnail_template": "https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/media/8/p/n/0/4/8pn04v7q3vfhf60wy173f6awcwkre1qj/artcraft_8pn04v7q3vfhf60wy173f6awcwkre1qj.mp4-thumb-{WIDTH}.gif" } }, "maybe_successfully_completed_at": "2026-06-24T04:17:30Z" }, "created_at": "2026-06-24T04:06:14Z", "updated_at": "2026-06-24T04:17:30Z" } } ``` β†’ The video is at `https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/media/8/p/n/0/4/8pn04v7q3vfhf60wy173f6awcwkre1qj/artcraft_8pn04v7q3vfhf60wy173f6awcwkre1qj.mp4` (example, not a live URL). **Example β€” completed image job** (from `POST /v1/omni_api/generate/image`): ```json { "maybe_result": { "entity_type": "media_file", "entity_token": "m_3kq9w0xv2hs5n8m4d6r1t8y0p2s4f6", "media_links": { "cdn_url": "https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/media/3/k/q/9/w/3kq9w0xv2hs5n8m4d6r1t8y0p2s4f6/artcraft_3kq9w0xv2hs5n8m4d6r1t8y0p2s4f6.jpg", "maybe_thumbnail_template": "https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/media/3/k/q/9/w/3kq9w0xv2hs5n8m4d6r1t8y0p2s4f6/artcraft_3kq9w0xv2hs5n8m4d6r1t8y0p2s4f6.jpg-thumb-{WIDTH}.jpg", "maybe_video_previews": null }, "maybe_successfully_completed_at": "2026-06-24T04:09:02Z" } } ``` β†’ The image is at `https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/media/3/k/q/9/w/3kq9w0xv2hs5n8m4d6r1t8y0p2s4f6/artcraft_3kq9w0xv2hs5n8m4d6r1t8y0p2s4f6.jpg` (example, not a live URL). ### Poll until complete #### bash ```bash JOB_TOKEN="jinf_xxxxxxxxxxxxxxxxxxxxxxxxx" while :; do resp=$(curl -s "http://localhost:12345/v1/omni_api/job_status/job/$JOB_TOKEN" \ -H "Authorization: Bearer YOUR_API_KEY") status=$(echo "$resp" | python3 -c "import sys,json;print(json.load(sys.stdin)['state']['status']['status'])") echo "status=$status" case "$status" in complete_success) echo "$resp" | python3 -c "import sys,json;print(json.load(sys.stdin)['state']['maybe_result']['media_links']['cdn_url'])" break ;; complete_failure|dead|cancelled_by_user|cancelled_by_system) echo "job ended without a result: $status"; break ;; esac sleep 5 done ``` #### python ```python import time import requests BASE_URL = "http://localhost:12345" # production: https://api.storyteller.ai API_KEY = "YOUR_API_KEY" JOB_TOKEN = "jinf_xxxxxxxxxxxxxxxxxxxxxxxxx" TERMINAL_FAILURES = {"complete_failure", "dead", "cancelled_by_user", "cancelled_by_system"} while True: resp = requests.get( f"{BASE_URL}/v1/omni_api/job_status/job/{JOB_TOKEN}", headers={"Authorization": f"Bearer {API_KEY}"}, timeout=30, ) resp.raise_for_status() state = resp.json()["state"] status = state["status"]["status"] print("status:", status, state["status"]["progress_percentage"], "%") if status == "complete_success": print("result URL:", state["maybe_result"]["media_links"]["cdn_url"]) break if status in TERMINAL_FAILURES: print("job ended without a result:", status) break time.sleep(5) ``` #### javascript ```javascript const BASE_URL = "http://localhost:12345"; // production: https://api.storyteller.ai const API_KEY = "YOUR_API_KEY"; const JOB_TOKEN = "jinf_xxxxxxxxxxxxxxxxxxxxxxxxx"; const TERMINAL_FAILURES = new Set([ "complete_failure", "dead", "cancelled_by_user", "cancelled_by_system", ]); const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); while (true) { const resp = await fetch(`${BASE_URL}/v1/omni_api/job_status/job/${JOB_TOKEN}`, { headers: { Authorization: `Bearer ${API_KEY}` }, }); const { state } = await resp.json(); const status = state.status.status; console.log("status:", status, state.status.progress_percentage, "%"); if (status === "complete_success") { console.log("result URL:", state.maybe_result.media_links.cdn_url); break; } if (TERMINAL_FAILURES.has(status)) { console.log("job ended without a result:", status); break; } await sleep(5000); } ``` --- ## Notes & troubleshooting - **Always send a fresh `idempotency_token`** (UUID). Reusing one is rejected as a duplicate. - **No cookies.** The endpoint authenticates only via the `Authorization` header. - **URL must be `http(s)`** and reachable by the server; redirects are followed. - **Type mismatches** (e.g. a `.webm` passed as a reference video, which requires `mp4`) are rejected with a `400` describing the unpermitted type. - **`401 Unauthorized`** β€” missing/invalid API key, or the key's owner is banned. - **`402 Payment required`** β€” insufficient credits/balance. - There is a companion image endpoint, `POST /v1/omni_api/generate/image`, which accepts `image_urls` (mutually exclusive with `image_media_tokens`) following the same rules.