Media Models

Video Generation

Video generation is asynchronous. POST /videos/generations returns a task_id immediately, then you poll GET /videos/tasks/{task_id} until the upstream finishes (typically 30–90 seconds, occasionally longer). If the task fails server-side, the gateway automatically refunds your balance, no manual action needed.

Quickstart

video.py
#60a5fa]">import os, time, requests

API = #60a5fa]">class="text-emerald-400">"https://api.thalam.ai/v1"
headers = {#60a5fa]">class="text-emerald-400">"Authorization": fclass="text-emerald-400">"Bearer {os.environ['THALAM_KEY']}"}

# 1. Submit
submit = requests.post(
    f#60a5fa]">class="text-emerald-400">"{API}/videos/generations",
    headers=headers,
    json={
        #60a5fa]">class="text-emerald-400">"model": class="text-emerald-400">"alibaba/wan-2.5-t2v-preview",
        #60a5fa]">class="text-emerald-400">"prompt": class="text-emerald-400">"A drone shot over a desert at golden hour.",
        #60a5fa]">class="text-emerald-400">"duration": 5,
    },
)
task_id = submit.json()[#60a5fa]">class="text-emerald-400">"task_id"]
#60a5fa]">print(fclass="text-emerald-400">"Submitted: {task_id}")

# 2. Poll
while True:
    poll = requests.get(f#60a5fa]">class="text-emerald-400">"{API}/videos/tasks/{task_id}", headers=headers).json()
    status = poll[#60a5fa]">class="text-emerald-400">"task"][class="text-emerald-400">"status"]
    #60a5fa]">if status == class="text-emerald-400">"TASK_STATUS_SUCCEED":
        #60a5fa]">print(fclass="text-emerald-400">"Video URL: {poll['videos'][0]['video_url']}")
        break
    #60a5fa]">if status == class="text-emerald-400">"TASK_STATUS_FAILED":
        # Balance is automatically refunded, no action needed.
        #60a5fa]">print(fclass="text-emerald-400">"Failed: {poll['task'].get('reason', 'unknown')}")
        break
    time.sleep(5)
video.ts
#60a5fa]">const API = class="text-emerald-400">"https://api.thalam.ai/v1";
#60a5fa]">const headers = {
  Authorization: `Bearer ${process.env.THALAM_KEY}`,
  #60a5fa]">class="text-emerald-400">"Content-Type": class="text-emerald-400">"application/json",
};

// 1. Submit
#60a5fa]">const submit = await fetch(`${API}/videos/generations`, {
  method: #60a5fa]">class="text-emerald-400">"POST",
  headers,
  body: JSON.stringify({
    model: #60a5fa]">class="text-emerald-400">"alibaba/wan-2.5-t2v-preview",
    prompt: #60a5fa]">class="text-emerald-400">"A drone shot over a desert at golden hour.",
    duration: 5,
  }),
});
#60a5fa]">const { task_id } = await submit.json();
console.log(`Submitted: ${task_id}`);

// 2. Poll
while (true) {
  #60a5fa]">const r = await fetch(`${API}/videos/tasks/${task_id}`, { headers });
  #60a5fa]">const poll = await r.json();
  #60a5fa]">const status = poll.task.status;
  #60a5fa]">if (status === class="text-emerald-400">"TASK_STATUS_SUCCEED") {
    console.log(`Video URL: ${poll.videos[0].video_url}`);
    break;
  }
  #60a5fa]">if (status === class="text-emerald-400">"TASK_STATUS_FAILED") {
    // Balance automatically refunded, no action needed.
    console.log(`Failed: ${poll.task.reason ?? #60a5fa]">class="text-emerald-400">"unknown"}`);
    break;
  }
  #60a5fa]">await new Promise((r) => setTimeout(r, 5000));
}
curl
# 1. Submit: returns a task_id
curl https://api.thalam.ai/v1/videos/generations \
  -H #60a5fa]">class="text-emerald-400">"Authorization: Bearer tl-xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H #60a5fa]">class="text-emerald-400">"Content-Type: application/json" \
  -d '{
    #60a5fa]">class="text-emerald-400">"model": class="text-emerald-400">"alibaba/wan-2.5-t2v-preview",
    #60a5fa]">class="text-emerald-400">"prompt": class="text-emerald-400">"A drone shot over a desert at golden hour.",
    #60a5fa]">class="text-emerald-400">"duration": 5
  }'
# → { class="text-emerald-400">"task_id": class="text-emerald-400">"abc123..." }

# 2. Poll: repeat every 5s until status is TASK_STATUS_SUCCEED or _FAILED
curl https://api.thalam.ai/v1/videos/tasks/abc123... \
  -H #60a5fa]">class="text-emerald-400">"Authorization: Bearer tl-xxxxxxxxxxxxxxxxxxxxxxxx"

Submit: request body

FieldTypeRequiredDefaultRangeDescription
modelstringyes—see model tableVideo model id from the catalog below.
promptstringyes—per-modelText describing the video to generate.
durationnumberoptional51 – 30 secondsOutput length in seconds. Pricing scales linearly relative to the base 5s clip; capped at 30s.
image_urlstringi2v only—HTTPS URLReference image URL for image-to-video models. Required for any model whose id ends in -i2v.
resolutionstringoptionalmodel default480p / 720p / 1080pOutput resolution. Supported set varies per model: see model details.
aspect_ratiostringoptionalmodel default16:9 / 9:16 / 4:3 / 3:4 / 1:1Frame aspect ratio. Supported set varies per model.
draftbooleanoptionalfalsebytedance/seedance-2.5Make a 480p draft that can later be finished at 1080p as the same shot. Priced as any 480p clip. See Draft mode below.
draft_task_idstringfinal onlynonea draft’s task_id, ≤ 7 days oldFinish that draft at 1080p. Send it with model only: the prompt, media, length, ratio and audio come from the draft. Priced as a 1080p clip.

Submit: response

The submit call returns a JSON envelope with the task_id you'll poll. Balance is pre-deducted at submit time and refunded automatically if the task ultimately fails.

submit response
{
  #60a5fa]">class="text-emerald-400">"task_id": class="text-emerald-400">"abc123def456...",
  #60a5fa]">class="text-emerald-400">"status": class="text-emerald-400">"processing"
}

Draft mode (Seedance 2.5)

Try ideas cheaply at 480p, then finish the one you like at 1080p. The final is the same shot: same framing, motion, seed and sound, rendered in full resolution, not a new roll. Send draft: true on a normal bytedance/seedance-2.5 request, poll it like any task, then send its task_id back as draft_task_id.

draft, then final
// 1. The draft: any Seedance 2.5 request, plus draft: true (480p only)
{ #60a5fa]">class="text-emerald-400">"model": class="text-emerald-400">"bytedance/seedance-2.5", class="text-emerald-400">"prompt": class="text-emerald-400">"…", class="text-emerald-400">"duration": 5, class="text-emerald-400">"ratio": class="text-emerald-400">"16:9", class="text-emerald-400">"draft": true }

// 2. The final: nothing but the draft's task_id (1080p only)
{ #60a5fa]">class="text-emerald-400">"model": class="text-emerald-400">"bytedance/seedance-2.5", class="text-emerald-400">"draft_task_id": class="text-emerald-400">"cgt-…" }
  • A draft costs the same as any 480p clip; its final costs the same as a direct 1080p clip of the same request.
  • A final must not repeat the prompt, media, duration, ratio, seed or generate_audio; it may set output_format and return_last_frame.
  • A draft can be finished for 7 days, once. Asking again returns 409 with the final’s task_id, so a retry never pays twice.
  • 1080p Seedance 2.5 video is 10-bit H.265 (HEVC). Some browsers and players without an HEVC decoder cannot play it.

Poll: endpoint

GET /videos/tasks/{task_id} : call every 5 seconds until the task settles. Response shape:

poll response
{
  #60a5fa]">class="text-emerald-400">"task": {
    #60a5fa]">class="text-emerald-400">"task_id": class="text-emerald-400">"abc123def456...",
    #60a5fa]">class="text-emerald-400">"status": class="text-emerald-400">"TASK_STATUS_PROCESSING",
    #60a5fa]">class="text-emerald-400">"reason": class="text-emerald-400">""
  },
  #60a5fa]">class="text-emerald-400">"videos": []
}

// On success:
{
  #60a5fa]">class="text-emerald-400">"task": { class="text-emerald-400">"status": class="text-emerald-400">"TASK_STATUS_SUCCEED", ... },
  #60a5fa]">class="text-emerald-400">"videos": [
    { #60a5fa]">class="text-emerald-400">"video_url": class="text-emerald-400">"https://...storage.../output.mp4" }
  ]
}

Status values

task.statusMeaningAction
TASK_STATUS_QUEUEDSubmitted, waiting for a worker.Keep polling.
TASK_STATUS_PROCESSINGWorker is generating the video.Keep polling.
TASK_STATUS_SUCCEEDDone. Result URL in videos[0].video_url.Stop polling and download the video.
TASK_STATUS_FAILEDUpstream rejected or hit an error mid-way.Stop polling. Balance is auto-refunded. Reason at task.reason.

Failed-task refund. When a task ends in TASK_STATUS_FAILED, the gateway atomically refunds the full pre-deducted amount within ~5 seconds. You'll see the credit on your dashboard ledger. No need to file a support ticket for failed renders.

Available models

Prices are for a base 5-second clip. Longer durations scale linearly (10s ≈ 2× base price, capped at 30s).

Model IDModePrice (5s base)Notes
alibaba/wan-2.5-t2v-previewt2v$0.50 / videoOpen-weight, strong on cinematic shots. Default 720p.
alibaba/wan2.6-t2vt2v$0.50 / videoNext iteration of Wan. Default 720p.
bytedance/seedance-2.5t2v · i2v · ref · editfrom $0.52 / videoFrontier: up to 30s single shots, 480p/720p/1080p @24fps. Draft mode: draft: true makes a 480p draft, draft_task_id finishes it at 1080p as the same shot. reference_images (≤30) / reference_videos (≤10) / reference_audios (≤10) with role-tagged inputs; first+last frame via image + last_image; integer-second timestamps in the prompt; native Arabic prompting & speech. Editing/extension: attach a reference video: aspect ratio (and duration for edits) follow the source automatically. Billed per generated frame; video-input tasks also bill input duration, declare input_video_seconds (else a 30s worst case is held). For 4K use Seedance 2.0. Safety review may reject prompts or inputs resembling copyrighted characters or real-person likeness (failed tasks auto-refund).
kuaishou/kling-v3.0-pro-t2vt2v$1.12 / videoFlagship motion fidelity. Highest-quality option.
kuaishou/kling-v3.0-pro-i2vi2v$1.12 / videoImage-to-video flagship. Requires image_url.

Common errors

StatusWhat it meansFix
400Invalid body: wrong duration, unsupported resolution, or i2v without image_urlCheck the per-model constraints in the table above.
402Insufficient balance for the requested durationTop up in the dashboard.
404task_id not found (during poll) or model not in catalogVerify the task_id is yours and submitted recently, or pick a model from the catalog above.
429Account-level rate limit (60 req/min)Slow down your polling cadence (5s is fine) or contact us.
502Upstream returned an unexpected response shapePaste x-upstream-request-id in a support ticket. Balance is preserved on submit-time failures.
504Upstream took too long to acknowledge submissionRetry. If persistent, the model may be under load, try another model.

Try it without code. The handles the submit + poll loop for you: pick a video model, type a prompt, hit Send, and watch the inline player when the render finishes.