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
#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)#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));
}# 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
| Field | Type | Required | Default | Range | Description |
|---|---|---|---|---|---|
| model | string | yes | — | see model table | Video model id from the catalog below. |
| prompt | string | yes | — | per-model | Text describing the video to generate. |
| duration | number | optional | 5 | 1 – 30 seconds | Output length in seconds. Pricing scales linearly relative to the base 5s clip; capped at 30s. |
| image_url | string | i2v only | — | HTTPS URL | Reference image URL for image-to-video models. Required for any model whose id ends in -i2v. |
| resolution | string | optional | model default | 480p / 720p / 1080p | Output resolution. Supported set varies per model — see model details. |
| aspect_ratio | string | optional | model default | 16:9 / 9:16 / 4:3 / 3:4 / 1:1 | Frame 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.
{
#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:
{
#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.status | Meaning | Action |
|---|---|---|
| TASK_STATUS_QUEUED | Submitted, waiting for a worker. | Keep polling. |
| TASK_STATUS_PROCESSING | Worker is generating the video. | Keep polling. |
| TASK_STATUS_SUCCEED | Done. Result URL in videos[0].video_url. | Stop polling and download the video. |
| TASK_STATUS_FAILED | Upstream 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 ID | Mode | Price (5s base) | Notes |
|---|---|---|---|
| alibaba/wan-2.5-t2v-preview | t2v | $0.50 / video | Open-weight, strong on cinematic shots. Default 720p. |
| alibaba/wan2.6-t2v | t2v | $0.50 / video | Next iteration of Wan. Default 720p. |
| bytedance/seedance-1.5-pro-t2v | t2v | $0.27 / video | Strong prompt adherence. Default 720p, supports up to 1080p. |
| bytedance/seedance-1.5-pro-i2v | i2v | $0.27 / video | Image-to-video version. Requires image_url. |
| kuaishou/kling-v3.0-pro-t2v | t2v | $1.12 / video | Flagship motion fidelity. Highest-quality option. |
| kuaishou/kling-v3.0-pro-i2v | i2v | $1.12 / video | Image-to-video flagship. Requires image_url. |
| kuaishou/kling-v2.6-pro-motion-control | motion ctrl | $0.35 / video | Motion-controlled variant. Specialized; consult docs. |
| minimax/hailuo-02 | — | Coming soon | Pricing finalising — listed once confirmed. |
| pixverse/pixverse-v4.5-t2v | t2v | $0.35 / video | Cheapest mid-tier option. Default 540p. |
| shengshu/vidu-q3-pro-t2v | t2v | $0.67 / video | Vidu family — extended-shot consistency. |
| tencent/hunyuan-video-fast | t2v | $0.30 / video | Fixed 5s, 1280×720 or 720×1280 only. |
Common errors
| Status | What it means | Fix |
|---|---|---|
| 400 | Invalid body — wrong duration, unsupported resolution, or i2v without image_url | Check the per-model constraints in the table above. |
| 402 | Insufficient balance for the requested duration | Top up in the dashboard. |
| 404 | task_id not found (during poll) or model not in catalog | Verify the task_id is yours and submitted recently, or pick a model from the catalog above. |
| 429 | Account-level rate limit (60 req/min) | Slow down your polling cadence (5s is fine) or contact us. |
| 502 | Upstream returned an unexpected response shape | Paste x-upstream-request-id in a support ticket. Balance is preserved on submit-time failures. |
| 504 | Upstream took too long to acknowledge submission | Retry. 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.