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
modelstringyessee model tableVideo model id from the catalog below.
promptstringyesper-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 onlyHTTPS 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.

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"
}

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-1.5-pro-t2vt2v$0.27 / videoStrong prompt adherence. Default 720p, supports up to 1080p.
bytedance/seedance-1.5-pro-i2vi2v$0.27 / videoImage-to-video version. Requires image_url.
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.
kuaishou/kling-v2.6-pro-motion-controlmotion ctrl$0.35 / videoMotion-controlled variant. Specialized; consult docs.
minimax/hailuo-02Coming soonPricing finalising — listed once confirmed.
pixverse/pixverse-v4.5-t2vt2v$0.35 / videoCheapest mid-tier option. Default 540p.
shengshu/vidu-q3-pro-t2vt2v$0.67 / videoVidu family — extended-shot consistency.
tencent/hunyuan-video-fastt2v$0.30 / videoFixed 5s, 1280×720 or 720×1280 only.

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.