apixly.ai

Video generation

Video generation is asynchronous: submit a task, poll its status, then download the result. This is apixly's own unified request format — the gateway translates it into each upstream provider's native format internally. Requires a key bound to a video group.

Seedance 2.5 · omni_reference_task_type

Seedance 2.5: optional omni_reference_task_type. reference creates a new video from references; edit changes the source video; extend extends it; auto lets the provider decide. Generations default to reference; edits default to edit. reference preserves requested duration/ratio. edit accepts 1–10 videos (4–30s each, at most 30s combined), 0–30 images and 0–10 audio clips, with duration=-1 and ratio=adaptive. Select the source in the prompt using @Video 1 or @Video 2; other videos are supporting references. Output duration and ratio follow the selected source, rather than a fixed first video; extend requires video and ratio=adaptive. duration=-1 alone does not select edit. Explicit action=edit/task_mode=edit remain supported; conflicting types are rejected. Prompt/type conflicts can return InvalidParameter.TaskTypeMismatch. No silent switch to edit. effective_parameters and parameter_adjustments show applied values; reservations are not final fees.

POST /v1/videos, /v1/video/generations → reference; POST /v1/videos/edits, /v1/video/edits → edit.

bash
# New video: preserve the requested duration and ratio.
curl https://apixly.ai/v1/video/generations \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "seedance-2.5",
    "prompt": "Create a new scene with the pictured character, using the reference video for camera movement.",
    "images": ["https://example.com/character.jpg"],
    "videos": ["https://example.com/reference.mp4"],
    "omni_reference_task_type": "reference",
    "duration": 10,
    "ratio": "16:9"
  }'

# Edit: 1–10 videos (4–30s each, <=30s combined), up to 30 images and 10 audio clips.
# The prompt selects the source: here @Video 2 is edited, @Video 1 is a supporting reference.
# A source video plus audio needs no additional image. Images/audio are forwarded together.
# POST /v1/video/edits remains an alias of the plural endpoint below.
curl https://apixly.ai/v1/videos/edits \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "seedance-2.5",
    "prompt": "Replace the character in @Video 2 with the character in @Image 1. Use @Video 1 as a reference for the background and @Audio 1 as the soundtrack. Preserve the timing and framing of @Video 2.",
    "videos": ["https://example.com/support-6s.mp4", "https://example.com/source-4s.mp4"],
    "images": ["https://example.com/character.jpg"],
    "audios": ["https://example.com/soundtrack.mp3"],
    "omni_reference_task_type": "edit",
    "duration": -1,
    "ratio": "adaptive",
    "resolution": "480p"
  }'
# input_seconds sums all input videos; audio seconds do not add to input_seconds/billed_seconds.
# The 4 + 6 second inputs reserve 10 input + ceil(10) output = 20 billed seconds.
# Reservation and settlement share the same rules and frozen rates; actual metering replaces estimates.
# Per-second: billed_seconds = actual_output_seconds + max(input_seconds, ceil(2 * actual_output_seconds / 3)).
# Token pricing uses upstream usage.completion_tokens with the same with_ref SKU.
# Actual output follows the prompt-selected source; after generation the difference is refunded/charged.

Wan 3.0 / Prime: video references, editing and extension

wan3.0-video and wan3.0-video-prime support text-to-video, first/last frames and freely combined reference images, videos and audio. A video reference does not require an image. You may omit mode; image2video also accepts video/audio-only references for both Wan models. Use videos[] or content with type=video_url and role=reference_video. In the prompt, identify references as 图1 / 视频1 / 音频1; numbering follows each media type separately. For Wan editing or extension, submit one reference video to /v1/videos and describe the intended change or continuation in the prompt; use ratio=adaptive for extension. omni_reference_task_type is a Seedance 2.5 option.

Wan accepts up to 10 reference images, 5 videos and 5 audio clips. Each video/audio clip must be 1–15s; the combined duration of each type must be ≤15s. Input-video duration + output duration must be ≤30s. Videos: MP4/MOV, ≥16fps, 240–4096px per side, ≤100MB each. Audio: WAV/MP3, ≤15MB each. Images: JPEG/PNG/BMP/WEBP, no transparency, 240–8000px per side, ≤20MB each; image/video aspect ratio must be ≤8:1. first_frame/last_frame cannot be mixed with any reference media; last_frame requires first_frame. Output: 2–30s or duration=-1, 480p/720p/1080p, ratio adaptive/16:9/9:16/1:1/4:3/3:4; 21:9 is unsupported. Public downloadable HTTP(S) URLs are required. The supplier validates media-file limits; /v1/video/config does not list every restriction.

Wan billing = (actual output_seconds × output rate + actual input_seconds × input-video rate) × group rate, using the actual output resolution and prices frozen at submission. When the two rates are equal, this is (input + output) seconds × rate. Images and audio have no separate fee; generate_audio does not change the price. The Seedance minimum input-duration rule does not apply. duration=-1 reserves the maximum output duration and settles actual usage. Reconcile output_cost, input_video_cost and settlement_status. If reference-video usage is missing, settlement_status=review: the result remains downloadable and reserved funds are held until reconciliation. Check current model prices; Prime has its own pricing.

bash
# Wan video reference: no accompanying image or mode is required.
# Both wan3.0-video and wan3.0-video-prime accept the same fields.
# Use public MP4/MOV URLs; up to 5 clips, each 1–15s, at most 15s combined.
# Input-video seconds + output seconds must not exceed 30s.
curl https://apixly.ai/v1/videos \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "wan3.0-video",
    "prompt": "Use 视频1 as the reference for camera movement; create a new cinematic forest scene.",
    "duration": 5,
    "resolution": "720p",
    "ratio": "16:9",
    "videos": ["https://example.com/source-3s.mp4"]
  }'
# For editing: use one video and describe the change in the prompt, e.g.
# "Transform 视频1 into a clay animation while preserving its composition."
# For extension: describe continuing 视频1 and use ratio=adaptive.
# These Wan requests use /v1/videos; omni_reference_task_type is for Seedance 2.5.
bash
# Wan content form: reference image + video + audio, with explicit roles.
# Do not combine content with flat image/images/video/videos/audio/audios fields.
# prompt may be omitted when content contains text. Per-type numbering starts at 1.
curl https://apixly.ai/v1/videos \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "wan3.0-video-prime",
    "content": [
      {"type": "text", "text": "Animate the character in 图1 using the camera movement in 视频1 and the voice in 音频1."},
      {"type": "image_url", "image_url": {"url": "https://example.com/character.jpg"}, "role": "reference_image"},
      {"type": "video_url", "video_url": {"url": "https://example.com/camera-3s.mp4"}, "role": "reference_video"},
      {"type": "audio_url", "audio_url": {"url": "https://example.com/voice-3s.mp3"}, "role": "reference_audio"}
    ],
    "duration": 5,
    "resolution": "720p",
    "ratio": "16:9",
    "generate_audio": true,
    "watermark": false,
    "seed": 0
  }'
# content is also supported on /v1/video/generations.
# Audio-only Wan references are accepted too; no image/video is required.
bash
# Wan first/last frames: last_frame requires first_frame.
# Do not add reference images, videos or audio to this request.
curl https://apixly.ai/v1/videos \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "wan3.0-video",
    "prompt": "A flower gradually opens between the first and last frame.",
    "first_frame": "https://example.com/closed.jpg",
    "last_frame": "https://example.com/open.jpg",
    "duration": 5,
    "resolution": "720p",
    "ratio": "adaptive",
    "generate_audio": false
  }'
# Equivalent content roles: first_frame and last_frame.
# Plain images[] means reference_image for Wan; use explicit frame fields/roles.
# Wan defaults: duration=5, resolution=720p (gateway), ratio=adaptive,
# generate_audio=true, watermark=false. duration=-1 requests automatic duration.

Discover capabilities first

Every model supports different modes, resolutions, durations and reference material. Always call GET /v1/video/config first and build your request from what it returns.

Capability fieldMeaning
modestext2video / image2video / frames2video
resolutionsAllowed resolution values, e.g. 480p / 720p / 1080p
max_input_imagesHow many reference images the model accepts
supports_reference_videoWhether a source video can be referenced
secondsAllowed duration range {min, max}
supports_reference_audioWhether reference audio is supported (passed via audios)
bash
curl https://apixly.ai/v1/video/config \
  -H "Authorization: Bearer YOUR_API_KEY"
# => {"object":"list","data":[
#   {"object":"video_model","model":"seedance-2.0",
#    "modes":["text2video","image2video","frames2video"],
#    "resolutions":["480p","720p","1080p"],"max_input_images":3,
#    "supports_reference_video":true,"supports_reference_audio":true,
#    "seconds":{"min":3,"max":12}}
# ]}
# Capabilities vary by model AND by account configuration — ALWAYS trust your own
# /v1/video/config response, not the sample values above.
# "resolutions" may list "4k" — in requests, "4k" and "2160p" are equivalent.

Request parameters

FieldRequiredDescription
modelyesVideo model ID from /v1/video/config
promptconditionalGeneration prompt; required unless supplied as a text element in content. Wan permits at most 20000 characters.
secondsnoDuration; string or number both accepted, e.g. "5"
durationnoOutput duration in seconds. Wan: integer 2–30, default 5, or -1 for automatic duration; Seedance also permits -1. MiniMax H3: 4–15; H3-Max: 5–15. Seedance 2.5 edit requires -1 and follows the selected source.
aspect_rationoe.g. 16:9
rationoService Inference aspect ratio (Wan, MiniMax and Seedance). Wan defaults to adaptive and rejects 21:9. Seedance 2.5 edit requires adaptive.
resolutionnoOne of the model's resolutions. /v1/video/config may list "4k" — in requests "4k" and "2160p" are equivalent
modenotext2video (default) / image2video / frames2video; frames2video requires exactly 2 images[] = [first, last]. Unknown mode values are silently ignored and re-inferred from reference images — do not invent mode names
image / images[]noReference image(s): URL or base64 data URI; some models (e.g. Seedance / Dreamina) accept only public http(s) URLs and reject base64 — check /v1/video/config Seedance 2.5 edit accepts up to 30 images alongside videos and audio.
videos[]noPublic reference-video URLs; requires supports_reference_video=true. Wan: up to 5 clips, 1–15s each, ≤15s combined; no image required. Seedance 2.5 edit: 1–10 clips, 4–30s each, ≤30s combined; select the source with @Video 1, etc. in the prompt. See the model-specific notes.
audios[]noPublic reference-audio URLs; requires supports_reference_audio=true. Wan: up to 5 clips, 1–15s each, ≤15s combined; audio-only references are accepted. Seedance 2.5: up to 10; audio-only generation is supported. Seedance 2.0 requires an image or video. Bind the prompt to the audio.
callback_urlnoTerminal-state webhook URL (optional): public http(s) address; we POST one JSON notification when the task reaches completed / failed. See the webhook section below for retries and signature
contentnoWan, MiniMax H3/H3-Max and Service Inference Seedance: array of text, image_url, video_url and audio_url items. Each media item contains a nested url and an optional role. Do not combine content with flat media fields; prompt and content text must agree if both are supplied.
first_frame / last_framenoWan / Service Inference: public first-frame and optional last-frame image URLs. last_frame requires first_frame. Do not combine frames with reference images, video or audio. Use ratio=adaptive to follow the source framing.
image_rolesnoService Inference: one role per images[] entry, in the same order: reference_image, first_frame or last_frame. Reference images cannot be mixed with first/last frames.
generate_audionoWan / Service Inference: boolean; Wan defaults to true. false produces a silent video at the same price. Reference audios[] is a separate input.
watermarknoWan / Service Inference: boolean; Wan defaults to false.
seednoWan / Service Inference Seedance: integer -1 or 0–2147483647. A fixed seed does not guarantee identical results.

Seedance 2.5 prompt references @Video 1, @Image 1 and @Audio 1 are numbered from 1. Explicitly select the source to edit and bind the references you want to use. Seedance 2.0 may require natural-language references to attached material. Legacy compatibility: the old top-level size and metadata.* nested forms are still accepted. When both forms appear, top-level fields win — with one exception: metadata.mode_type overrides mode; media fields (image/images/videos/audios and their metadata.* forms) are MERGED as a union, not overridden.

Create a task: text to video

Fields are flat at the top level, and seconds is a string:

POST /v1/videos and POST /v1/video/generations are exact aliases — every method (create, poll, list, download) exists under both paths with identical behavior. Your key must be bound to a video group; video groups use the provider name as their platform (e.g. seedance / veo / kling), configured by the administrator — there is no literal "video" platform value.

bash
curl https://apixly.ai/v1/videos \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "seedance-2.0",
    "prompt": "A golden retriever puppy running on a sunny beach, cinematic",
    "seconds": "5",
    "aspect_ratio": "16:9",
    "resolution": "720p"
  }'
# => {"id":"...","object":"video","status":"queued","progress":0,"seconds":"5"}

Image to video

Set mode to image2video and pass the source image as a URL or base64 data URI. Some models only accept public URLs. For multiple reference images use the top-level images array.

bash
curl https://apixly.ai/v1/videos \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "seedance-2.0",
    "mode": "image2video",
    "prompt": "The cat slowly turns its head and smiles",
    "image": "https://example.com/cat.png",
    "seconds": "5",
    "resolution": "720p"
  }'

First / last frame (frames2video)

mode: "frames2video" interpolates between two key frames. Pass EXACTLY 2 reference images in images[], in order [first_frame, last_frame] — any other count fails validation with an explicit error. Only models whose modes include frames2video support it (check /v1/video/config).

bash
# First/last-frame mode: mode = "frames2video" + EXACTLY 2 reference images,
# in order [first_frame, last_frame]. Any other count fails validation.
curl https://apixly.ai/v1/videos \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "seedance-2.0",
    "prompt": "smooth cinematic transition from day to night",
    "mode": "frames2video",
    "images": ["https://example.com/first.jpg", "https://example.com/last.jpg"]
  }'

Reference audio

Pass audio URLs in audios[] only for supports_reference_audio=true models. Use public http(s) links and explicitly refer to the attached audio in the prompt.

Reference audio (supported models only): public http(s) URLs. Seedance 2.5 accepts up to 10 clips: generations may use audio without images or video; edits need a source video and no additional image. Seedance 2.0 needs a reference image or video. Explicitly refer to the attached audio in the prompt.

What to expect: seedance-2.0 mixes the reference music into the soundtrack (with automatic sound effects on top); for strong lip-sync / singing use the seedance-2.0 family.

bash
curl https://apixly.ai/v1/videos \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "seedance-2.0",
    "mode": "image2video",
    "prompt": "the cat dances breakdance strictly to the attached reference music — use the reference audio as the soundtrack, every move on its rhythm",
    "images": ["https://example.com/cat.jpg"],
    "audios": ["https://example.com/music.mp3"],
    "seconds": "6",
    "resolution": "480p"
  }'

Seedance 2.5 prompt references @Video 1, @Image 1 and @Audio 1 are numbered from 1. Explicitly select the source to edit and bind the references you want to use. Seedance 2.0 may require natural-language references to attached material.

Poll status

FieldMeaning
statusqueued → in_progress → completed | failed
progressProgress percentage, 0–100 (integer)
task_id / idTask ID (string) — the same value; deduplicate by it
created_at / completed_atUnix seconds: creation time / terminal time (note: image jobs use "created" instead)
seconds / sizeRequested duration / frame size when supplied; on completed Seedance 2.5 tasks, seconds is the actual measured output duration, including fractional seconds.
urlTemporary download link on completion (valid about 30 minutes, freshly signed per poll)
error.code / error.messageStable failure code (see the failure-types table below) + readable reason, when status = failed
skuBilling SKU label like "480p_with_ref" — present only for token / per-second-with-reference billed models
usage{completion_tokens, total_tokens} reported by the provider — present only when non-zero; used for reconciliation
input_seconds / billed_secondsSeedance 2.5: input_seconds sums all input videos. billed_seconds is the final billable duration with per-second pricing; token pricing uses usage.completion_tokens.
settlement_statusSeedance 2.5: settled = final settlement complete; review = billing awaits authoritative usage or required output metering. The result remains downloadable and the reservation is held pending reconciliation.
output_seconds / input_seconds / output_cost / input_video_cost / input_image_costWan / MiniMax: actual metering and separate cost components after group rate. settlement_status=review preserves download and reservation until authoritative usage is available.
bash
curl https://apixly.ai/v1/videos/VIDEO_ID \
  -H "Authorization: Bearer YOUR_API_KEY"
# status: queued -> in_progress -> completed | failed
# on completion the response carries a temporary public URL (~30 min)
json
{
  "id": "video_01ABC...",
  "object": "video",
  "status": "completed",
  "progress": 100,
  "seconds": "5",
  "url": "<TEMPORARY_DOWNLOAD_URL>"
}

Failure types (error.code)

When a task ends with status=failed, the error object carries a stable error.code alongside the human-readable error.message. Branch your handling on code — never on message text, which may change at any time.

Fix parameters, unavailable references, task_type_mismatch, entitlement or balance before resubmitting. Retry transient errors with bounded backoff only after checking the existing task; never treat an unknown submission as a free failure.

error.codeMeaning & handling
content_rejectedContent rejected by moderation (a verdict, not a service glitch). Do not retry.
reference_unavailableA reference material (image / video / audio) could not be downloaded or used. Fix the material and resubmit.
invalid_requestInvalid request parameters (duration / resolution / prompt length …). Fix the request before retrying.
plan_not_entitledYour key or group is not entitled to this model or capability.
insufficient_balanceInsufficient balance or quota.
timeoutA timeout or 5xx does not prove no task was created or no supplier work occurred. Check the existing task and request ID before a new submission; retry only after resolving a confirmed terminal failure.
upstream_errorA timeout or 5xx does not prove no task was created or no supplier work occurred. Check the existing task and request ID before a new submission; retry only after resolving a confirmed terminal failure.
internal_errorA timeout or 5xx does not prove no task was created or no supplier work occurred. Check the existing task and request ID before a new submission; retry only after resolving a confirmed terminal failure.
task_type_mismatchFix parameters, unavailable references, task_type_mismatch, entitlement or balance before resubmitting. Retry transient errors with bounded backoff only after checking the existing task; never treat an unknown submission as a free failure.
json
{
  "id": "video_01ABC...",
  "object": "video",
  "status": "failed",
  "error": {
    "code": "content_rejected",
    "message": "OutputVideoSensitiveContentDetected: The request failed because the output video may contain sensitive information"
  }
}

Download the result

Fetch the temporary URL from the completed response, or stream through the authenticated content endpoint:

bash
curl -L -o result.mp4 \
  "https://apixly.ai/v1/videos/VIDEO_ID/content" \
  -H "Authorization: Bearer YOUR_API_KEY"

Videos are zero-storage: the result is only available while the upstream keeps it (the temporary link lasts around 30 minutes). Download promptly after completion.

List your tasks

GET /v1/videos lists your own tasks, newest first. Optional query: page (default 1), page_size (1–100, default 20; out-of-range falls back to 20), status, provider. The response is {"object":"list","data":[…],"total","page","page_size"} where data[] entries have the same shape as GET /v1/videos/{id} — completed entries include their download url.

bash
# List your own video tasks (newest first). Query: page (default 1),
# page_size (1-100, default 20), status, provider — all optional.
curl "https://apixly.ai/v1/videos?page=1&page_size=20" \
  -H "Authorization: Bearer YOUR_API_KEY"
# => {"object":"list","data":[<same objects as GET /v1/videos/{id}>],
#     "total":42,"page":1,"page_size":20}
# Completed entries include their download "url".
# GET /v1/video/generations is an exact alias of GET /v1/videos.

Webhook callback (optional)

Besides polling, you can pass an optional callback_url when creating a task. Each task gets exactly one terminal-state notification: we POST a JSON message — task_id, status (completed / failed), the download url on success, error.code / error.message on failure — to your endpoint. Omit callback_url and nothing changes: polling stays the default.

Delivery is at-least-once and retried: if your endpoint does not answer 2xx we retry 3 times with growing gaps (10s / 1m / 5m), then give up. Handle duplicates idempotently by task_id. The callback complements polling — the GET status endpoint remains the source of truth.

  • callback_url must be a public http(s) URL — private/internal addresses are rejected with 400; https is recommended.
  • Download URLs inside the callback are freshly signed per delivery and valid for 30 minutes — fetch promptly.
  • Unified endpoints only (/v1/videos, /v1/video/generations); provider-native endpoints reject callback_url with 400 (provider-native paths are internal compatibility endpoints, e.g. Kling-style routes — not part of the publicly documented API; if you only use the endpoints on this page you will never hit them).
  • Billing is fully independent of the webhook: a successful generation is charged as usual whether or not the callback can be delivered; callback failures never change task state or billing.
  • Field-naming note: webhooks (both video and image) use created_at / completed_at; the video terminal status is "completed" while the image terminal status is "succeeded" — same as each API's polling endpoint.
FieldDescription
HTTP header X-Webhook-SignatureSignature: t=<unix seconds>,v1=hex(HMAC-SHA256(your API key, t + "." + raw body)). Verification optional but recommended
HTTP header X-Webhook-AttemptDelivery attempt number, 1–4
objectAlways "video"
task_id / idTask ID (string) — identical to the id returned when you created the task; deduplicate by it
modelThe model you requested
status"completed" or "failed" — same enum as the polling endpoint
urlSuccess only: video download URL, freshly signed per delivery, valid 30 minutes
error.code / error.messageFailure only: stable error code (same enum as the failure-types table above) plus a human-readable message
created_at / completed_atUnix seconds: task creation time / terminal time
bash
curl https://apixly.ai/v1/videos \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "grok-imagine-video",
    "prompt": "a paper boat drifting on a calm pond",
    "seconds": "6",
    "resolution": "480p",
    "callback_url": "https://your-server.example.com/webhooks/video"
  }'
json
// POST to your callback_url on the terminal state. Headers we send:
//   Content-Type: application/json        User-Agent: sub2api-webhook/1.0
//   X-Webhook-Signature: t=<unix>,v1=<hex hmac>   X-Webhook-Attempt: 1..4
// completed:
{
  "object": "video",
  "task_id": "123",
  "id": "123",
  "model": "grok-imagine-video",
  "status": "completed",
  "created_at": 1785772215,
  "completed_at": 1785772250,
  "url": "https://apixly.ai/v1/videos/content?ref=..."   // valid 30 min from delivery
}
// failed:
{
  "object": "video",
  "task_id": "124",
  "status": "failed",
  "created_at": 1785772215,
  "completed_at": 1785772300,
  "error": { "code": "upstream_error", "message": "..." }
}

Webhook signature & how to verify it

The signing secret is simply the API key you created the task with — there is no separate webhook secret to provision. Every delivery carries a signature header like X-Webhook-Signature: t=1754500000,v1=8f2a…c9, where t is the unix timestamp at send time and v1 is the signature: the hex HMAC-SHA256 of the string t + "." + <raw request body bytes>, keyed with your API key.

Verification is optional: skip it by ignoring the header — deliveries behave the same. To verify, follow these four steps (recommended in production):

  • Step 1: read t (timestamp) and v1 (signature value) from the header.
  • Step 2: compute HMAC-SHA256 keyed with your API key over t + "." + the raw request body bytes, hex-encoded. Use the exact bytes received — do not parse and re-serialize the JSON first.
  • Step 3: compare your result with v1 using a constant-time comparison: match = verified; mismatch, or no signature header at all = discard.
  • Step 4: check that t is close to the current time (≤ 5 minutes recommended); discard stale requests to block replays.
python
# Optional: verify X-Webhook-Signature (recommended — reject unsigned
# requests and stale timestamps if you enable verification).
import hashlib, hmac, time

def verify(header: str, body: bytes, api_key: str, max_age: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
    t, v1 = parts.get("t", ""), parts.get("v1", "")
    if not t or not v1:
        return False
    if abs(time.time() - int(t)) > max_age:  # replay protection
        return False
    mac = hmac.new(api_key.encode(), f"{t}.".encode() + body, hashlib.sha256)
    return hmac.compare_digest(mac.hexdigest(), v1)

The signing key is the API key that created the task — keep it server-side only. If that key is deleted before a task finishes, its callbacks are still delivered but WITHOUT a signature header. So if you reject unsigned requests, accept that deleting a key drops its in-flight callbacks; otherwise handle unsigned callbacks by looking the task_id up yourself before trusting them. The payload carries no key identifier — either dedicate one callback_url per key, or map task_id back to the key that created it.

Seedance 2.5 · Editing and billing

Requests containing video use the with_ref SKU, frozen at creation together with the unit price and group rate. Token pricing settles upstream usage.completion_tokens, including minimum usage, using the same SKU.

input_seconds sums every source and supporting video. Images and audio have no separate surcharge; audio seconds do not add to input_seconds or billed_seconds.

Per-second pricing: billed_seconds = actual_output_seconds + max(input_seconds, ceil(2 × actual_output_seconds / 3)). Output duration is measured from the result MP4, preserving fractional seconds. Cost = billed_seconds × frozen per-second price × group rate. Token pricing instead uses reported usage.completion_tokens × frozen token price × group rate.

Reservation and final settlement reuse the same calculation rules and frozen rates. Edit output reservation = ceil(input_seconds). For a 4s source plus 6s support: input_seconds = 4 + 6 = 10; input + reserved output = 10 + ceil(10) = 20 billed seconds. The prompt still selects which source is edited.

After generation, actual metering replaces the estimate and the difference is refunded or charged. The reserved and final amounts can differ because actual output is known only after generation.

If authoritative upstream usage is unavailable, or per-second pricing lacks valid measured output, settlement_status=review. The completed result remains downloadable and the reservation is held pending reconciliation.

json
{
  "id": "video_01ABC...",
  "object": "video",
  "model": "seedance-2.5",
  "status": "completed",
  "progress": 100,
  "seconds": "3.712",
  "sku": "480p_with_ref",
  "usage": {"completion_tokens": 131702},
  "input_seconds": 10,
  "billed_seconds": 13.712,
  "settlement_status": "settled",
  "url": "<TEMPORARY_DOWNLOAD_URL>"
}

Billing

Video requires a balance-based group; subscription groups are unsupported. Charges follow the model and channel pricing: per second, per token or distinct input/output/image components. Use current model prices and terminal usage for reconciliation; the initial reservation is an estimate.

Seedance 2.0 only — Seedance 2.0 billing: with token pricing, cost = tokens consumed × SKU unit price × group rate. The SKU is resolution + reference-video flag (e.g. 480p_no_ref / 480p_with_ref) and follows the provider's actual metering — if a request carries a reference video but the model does not actually use it, no_ref applies. Token usage ≈ duration × width × height × 24fps ÷ 1024, with the returned usage.completion_tokens as the source of truth; with a reference video, input-video duration also counts and a minimum charge applies (input counted as at least 2/3 of the output duration, rounded up). The sku and usage fields in the task response let you reconcile charges. Per-second billed models follow the same rule: tasks with a reference video are charged on (input + output) duration, with input counted as at least 2/3 of the output duration (rounded up).

Studio shows the model’s live price before you create a task.

Complete examples

Three copy-paste-ready end-to-end examples: one script for submit → poll → download; a remix using all three materials (image + video + audio); extending an existing clip.

python
# Python 3 standard library: submit once, poll for at most 10 minutes, download.
# Keep video-request.json if interrupted; never restart by automatically POSTing again.
# For an intentional new generation, choose a new receipt filename below.
import json, pathlib, time, urllib.parse, urllib.request, uuid

BASE = "https://apixly.ai"
API_KEY = "YOUR_API_KEY"
payload = {"model": "seedance-2.0", "prompt": "a paper boat drifting down a rainy street, cinematic",
           "seconds": "5", "resolution": "720p"}
receipt = {"client_request_id": uuid.uuid4().hex, "request": payload}
receipt_path = pathlib.Path("video-request.json")
job_id = None

def preserve_receipt():
    receipt_path.write_text(json.dumps(receipt, indent=2), encoding="utf-8")

def request_json(url, data=None, timeout=30):
    headers = {"Authorization": "Bearer " + API_KEY}
    if data is not None:
        headers["Content-Type"] = "application/json"
    req = urllib.request.Request(url, data=json.dumps(data).encode() if data is not None else None,
                                 headers=headers)
    with urllib.request.urlopen(req, timeout=timeout) as response:
        request_id = response.headers.get("X-Request-ID")
        if request_id:
            receipt.setdefault("request_id", request_id)
            receipt["last_request_id"] = request_id
            preserve_receipt()
            print("request_id=" + request_id)
        result = json.loads(response.read())
    if not isinstance(result, dict):
        raise ValueError("Malformed task response")
    return result

try:
    if receipt_path.exists():
        previous = json.loads(receipt_path.read_text(encoding="utf-8"))
        if isinstance(previous, dict):
            receipt = previous
            job_id = previous.get("job_id")
        raise RuntimeError("An existing request receipt must be reconciled before a new submission")
    print("client_request_id=" + receipt["client_request_id"])
    preserve_receipt()  # Save a local marker before a possibly ambiguous submission.
    task = request_json(BASE + "/v1/videos", payload)
    job_id = task.get("id")
    if not isinstance(job_id, str) or not job_id.strip() or job_id.strip().lower() in ("null", "none"):
        raise ValueError("Submission did not return a usable job ID")
    job_id = job_id.strip()
    receipt["job_id"] = job_id
    preserve_receipt()
    print("job_id=" + job_id)
    deadline = time.monotonic() + 600
    for attempt in range(121):
        status = task.get("status")
        print("status=" + str(status))
        if status == "completed":
            result_url = task.get("url")
            if not isinstance(result_url, str):
                raise ValueError("Completed task has no download URL; query this job again")
            parsed = urllib.parse.urlsplit(result_url)
            if parsed.scheme not in ("http", "https") or not parsed.hostname or parsed.username or parsed.password:
                raise ValueError("Invalid download URL")
            with urllib.request.urlopen(result_url, timeout=60) as response:
                content_type = response.headers.get("Content-Type", "").split(";")[0].strip()
                if content_type and not (content_type.startswith("video/") or content_type == "application/octet-stream"):
                    raise ValueError("Download did not return video content")
                video = response.read()
            if not video:
                raise ValueError("Empty video download")
            pathlib.Path("result.mp4").write_bytes(video)
            print("Saved result.mp4; job_id=" + job_id)
            break
        if status == "failed":
            raise RuntimeError("Generation failed: " + json.dumps(task.get("error", {})))
        if status not in ("queued", "in_progress"):
            raise ValueError("Unknown or missing task status")
        remaining = deadline - time.monotonic()
        if attempt == 120 or remaining <= 0:
            raise TimeoutError("Local polling limit reached")
        if attempt:
            time.sleep(min(5, remaining))
        remaining = deadline - time.monotonic()
        if remaining <= 0:
            raise TimeoutError("Local polling limit reached")
        task = request_json(BASE + "/v1/videos/" + urllib.parse.quote(job_id, safe=""), timeout=min(30, remaining))
except Exception as error:
    raise SystemExit("client_request_id=" + str(receipt.get("client_request_id", "unknown")) + "; job_id=" + str(job_id) +
                     "; " + str(error) + ". Keep " + str(receipt_path) + " and query the existing job or contact support. Do not auto-resubmit.")
bash
# Use case: remix a reference video — add the character from the image, dance to the attached music.
# Note how the prompt explicitly references ALL three attached materials in natural language.
curl https://apixly.ai/v1/videos \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "seedance-2.0",
    "mode": "image2video",
    "prompt": "add the orange cat from the reference image into the reference video. the animals dance breakdance strictly to the attached reference music — use the reference audio as the soundtrack, every move on its rhythm",
    "images": ["https://example.com/cat.jpg"],
    "videos": ["https://example.com/dance.mp4"],
    "audios": ["https://example.com/music.wav"],
    "seconds": "6",
    "resolution": "480p",
    "aspect_ratio": "9:16"
  }'
bash
# Use case: continue an existing clip (video extension) — supported models only
curl https://apixly.ai/v1/videos \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "grok-imagine-video",
    "prompt": "continue the scene: the camera slowly pulls back to reveal the whole valley at sunset",
    "videos": ["https://example.com/source.mp4"],
    "seconds": "8",
    "resolution": "720p"
  }'

Additional endpoints and model limits · Seedance assets

Check model access and account enablement; endpoint registration alone does not mean every model or account supports it.

POST /v1/videos/assets: model, URL, AssetType (Image/Video/Audio), optional Name. GET /v1/videos/assets/{id}: wait for data.status=Active, then use data.ref (asset://gva-...). Seedance Max accounts only; not Wan. Handles are scoped to the creating user/API key/group/account, expire after 7 days, and pin generation to that account. Upload does not debit generation fees.

bash
curl https://apixly.ai/v1/videos/assets \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"seedance-2.5","URL":"https://example.com/source.mp4","AssetType":"Video","Name":"source"}'
# Poll with the same key until data.status=Active; use returned data.ref.
curl https://apixly.ai/v1/videos/assets/ASSET_ID -H "Authorization: Bearer YOUR_API_KEY"

Additional endpoints and model limits · MiniMax / Seedance

Check model access and account enablement; endpoint registration alone does not mean every model or account supports it.

Service Inference contracts only. MiniMax H3: first 5 images free; H3-Max: first 2 free, with a distinct input-video price required. Input/output/image components are metered separately; use actual supplier usage and frozen rates. H3-Max Turbo and other suppliers keep their own contracts.

ModelResolutionOutput secondsReference image/video/audio maxima
minimax-h3768p / 1440p4–159 / 3 / 3; total ≤12
minimax-h3-max480p / 768p5–159 / 3 / 3; total ≤12
Seedance 2.0 / mini480p / 720p; full 2.0: 1080p / 4k4–15 or -19 / 3 / 3
Seedance 2.5480p / 720p / 1080p4–30 or -130 / 10 / 10

Additional model options

FieldModel-specific behavior
return_last_frameSeedance / MiniMax: request an output last-frame image when supported; read last_frame_url. Wan ignores this option; it is distinct from the input last_frame.
output_formatSeedance 2.5: mp4 or mov; not a generic Wan output-format switch.
execution_expires_afterService Inference Seedance: integer 3600–259200 seconds.
extraMiniMax H3 / H3-Max: supplier option object, e.g. {"prompt_expansion_mode":"quality"}; supported keys depend on the supplier.
input_preprocessingSeedance 2.5: optional {validate, normalize_video_fps, pad_images} booleans. Strict inspection, normalization to 30fps and centered image padding are opt-in. No clipping, speed change or cropping; effective_parameters / parameter_adjustments report changes.

Model-specific request contracts

Discover available IDs with GET /v1/models using your key. The public price catalog is not a guarantee of current routing. Existing valid requests need no changes; optional fields are model-specific.

Native Veo/Kling/Vidu/Ark/Sora integrations are conditional on actual key/account availability; they are not automatically public products. Native Veo accepts one base64 image, not a public image URL; native Ark supports only implemented text/image content. Do not infer another provider’s reference-media features from a similar model name.

A timeout or 5xx does not prove no task was created or no supplier work occurred. Check the existing task and request ID before a new submission; retry only after resolving a confirmed terminal failure.

ModelRequest contract
grok-imagine-video · grok-imagine-video-1.5-preview480p/720p; up to 15s. Standard supports up to 7 reference images (image-reference output at most 10s); 1.5-preview accepts 1. No reference audio. Only one source video for edits/extensions; multiple videos are rejected. For edits describe the change and omit output duration; extensions use the source and continuation prompt.
happyhorse-1.13–15s, 480p/720p/1080p, up to 9 reference images; no video/audio references. No image selects text-to-video; images select reference generation, even a single image. A single image does not implicitly mean first_frame. Use first-frame behavior only when your mapped model/config supports it. Defaults: 5s,720p,watermark=false.
minimax-h3 · minimax-h3-discount · minimax-h3-max · minimax-h3-max-turboDo not share one contract across H3 variants. H3: 4–15s,768p/1440p,9 images/3 videos/3 audio,12 files combined. H3-Max: 5–15s,480p/768p,same counts,distinct input-video tariff. H3-Max Turbo: 5–15s,480p/768p,at most 2 first/last-frame images,no video/audio references. H3-discount: 5–15s,720p/768p/1080p,up to 9 images and audio references,no video references; continuous megapixels/clarity controls are not applied.
seedance-2.0 · seedance-2.0-mini · seedance-2.5Use current public seedance-2.0/seedance-2.0-mini/seedance-2.5 IDs. Video references, first/last frames and audio rules differ by model/provider; use the earlier parameter and task-type sections. Old HC/discount model names are not current public choices. Do not apply Seedance input-duration billing floors to Wan/MiniMax.
wan3.0-video · wan3.0-video-primeVideo/audio-only and mixed references are supported; see the Wan section for counts, roles, file limits and separate input/output billing. image2video no longer requires an accompanying image when Wan video/audio references are supplied.