Quickstart
Get up and running with Viddra in under 5 minutes — create an account, mint an API key and generate your first video with Wan 2.6.
Create your API key
Sign up, then open Console → API Keys and create a secret key. The plaintext key is shown once — store it somewhere safe.
Make your first call
Submit a generation task. You get a task id back immediately (202 Accepted) while the model runs asynchronously.
curl -X POST https://api.viddra.com/v1/video/generations \
-H "Authorization: Bearer $VIDDRA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "wan2.6",
"prompt": "A cinematic shot of a neon-lit city street in the rain, ultra-detailed, 4k",
"duration": 5,
"resolution": "720p",
"aspect_ratio": "16:9"
}'
# → 202 Accepted
# {"id":"…","task_id":"…","object":"video.generation",
# "status":"queued","model":"wan2.6","hold_usd":"0.6000", …}Poll and download
Video generation takes a minute or two. Poll the task endpoint until the status flips to succeeded, then grab your video URL. Failed tasks refund automatically.
# Poll every 3–5 seconds until status is succeeded / failed
curl https://api.viddra.com/v1/video/generations/<id> \
-H "Authorization: Bearer $VIDDRA_API_KEY"
# → {"status":"succeeded","video_url":"https://…","cost_usd":"0.6000", …}
# Failed tasks: {"status":"failed","error":"…"} — the held amount is
# automatically refunded to your balance.Authentication
Generation endpoints (/v1/video/generations, /v1/image/generations) authenticate with an API key. Console endpoints (keys, billing) use the JWT returned by POST /v1/auth/login. Both travel in the Authorization header:
Authorization: Bearer vsk-xxxxxxxxxxxxxxxxxSecurity Warning
Never expose your API keys in shipped client-side code. Route requests through your own backend to protect your quota and credentials. Keys can be rotated or revoked instantly in the console.
Models
The full model directory is always live at GET /v1/models. The table below lists the current line-up; the API response is the source of truth for pricing and availability.
| ID | Name | Type | Price | Notes |
|---|---|---|---|---|
| wan2.6 | Wan 2.6 | Video | $0.16/sec | 720p · $0.24/sec 1080p |
| kling3.0 | Kling 3.0 | Video | $0.27/sec | 3–15s, optional audio |
| seedance | Seedance 1.0 Pro | Video | $0.13/sec | budget tier |
| seedance2.0 | Seedance 2.0 | Video | $0.47/sec | 480p–4K tiers |
| seedance2.5 | Seedance 2.5 | Video | $0.68/sec | up to 30s |
| seedance2.0-mini | Seedance 2.0 Mini | Video | $0.15/sec | 480p $0.08/sec · 4-15s budget |
| veo3 | Veo 3.1 | Video | $0.35/sec | $0.60/sec with audio |
| veo3.1-fast | Veo 3.1 Fast | Video | $0.24/sec | speed-optimized |
| flux | FLUX.1 [dev] | Image | $0.05/img | open-weights |
| flux2-pro | FLUX.2 Pro | Image | $0.06/img | next-gen detail |
| seedream | Seedream 4.0 | Image | $0.05/img | hi-res + text |
| qwen-image | Qwen Image | Image | $0.03/img | typography |
| ideogram-3.0 | Ideogram 3.0 | Image | $0.10/img | best in-image text |
| hailuo-2.3 | Hailuo 2.3 | Video | $0.0933/sec | 768P · $0.15/sec 1080P |
| hailuo-h3 | Hailuo H3 | Video | $0.12/sec | 768P · $0.20/sec 2K |
| speech-02-hd | Speech 02 HD | Audio | $0.18/kchar | studio-grade TTS |
| speech-2.8-hd | Speech 2.8 HD | Audio | $0.18/kchar | 19 emotions · 40 langs |
| wan2.2 | Wan 2.2 | Video | retired | → wan2.6 |
| kling2.1 | Kling 2.1 | Video | retired | → kling3.0 |
Retired models return MODEL_RETIRED (400) and no longer accept new tasks.
Video API
/v1/video/generationsStarts an asynchronous video generation task. The estimated cost (hold_usd) is frozen from your balance and settled to the real cost_usd on completion — or refunded on failure. Available models: wan2.6, kling3.0, seedance, seedance2.0, seedance2.5, seedance2.0-mini, veo3, veo3.1-fast, hailuo-2.3, hailuo-h3.
Request Body
{
"model": "kling3.0", // required — see GET /v1/models
"prompt": "Cinematic dolly shot through a neon market…", // required
"duration": 5, // optional: 5 or 10 (seconds)
"resolution": "1080p", // optional: "720p" | "1080p"
"aspect_ratio": "16:9", // optional: "16:9" | "9:16" | "1:1"
"audio": false // optional: native audio when supported
}Response (202 Accepted)
HTTP/1.1 202 Accepted
{
"id": "9f3c…",
"task_id": "viddra-…",
"object": "video.generation",
"status": "queued",
"model": "kling3.0",
"prompt": "Cinematic dolly shot through a neon market…",
"duration_seconds": 5,
"hold_usd": "1.3500",
"created_at": "2026-08-20T11:24:07.000Z"
}Image API
/v1/image/generationsSame async contract as video: submit, poll, download. Available models: flux, seedream, qwen-image.
Request Body
{
"model": "flux", // required — flux | flux2-pro | seedream | qwen-image | ideogram-3.0
"prompt": "Editorial portrait of an astronaut in a 1970s diner, film grain",
"image_size": "1024x1024", // optional
"num_images": 1 // optional
}Response (202 Accepted)
HTTP/1.1 202 Accepted
{
"id": "7ad1…",
"task_id": "viddra-…",
"object": "image.generation",
"status": "queued",
"model": "flux",
"prompt": "Editorial portrait of an astronaut…",
"hold_usd": "0.0500",
"created_at": "2026-08-20T11:26:41.000Z"
}Audio API
/v1/audio/speechText-to-speech powered by MiniMax Speech 02 HD. The provider account is being provisioned — calls currently return 503 PROVIDER_NOT_READY and no balance is held.
curl -X POST https://api.viddra.com/v1/audio/speech \
-H "Authorization: Bearer $VIDDRA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "speech-02-hd", "input": "Hello from Viddra!"}'
# Current status: 503 PROVIDER_NOT_READY
# Speech models are being provisioned and will go live shortly.Task Polling
/v1/video/generations/{id}Retrieve task status with your API key (image tasks: /v1/image/generations/{id}). Poll every 3–5 seconds. Statuses: queued → running → succeeded / failed.
Response (succeeded)
{
"id": "9f3c…",
"task_id": "viddra-…",
"status": "succeeded",
"model": "kling3.0",
"video_url": "https://…/result.mp4",
"result_url": "https://…/result.mp4",
"hold_usd": "1.3500",
"cost_usd": "1.3500",
"created_at": "2026-08-20T11:24:07.000Z",
"completed_at": "2026-08-20T11:25:52.000Z"
}
# On failure:
# {"status":"failed","error":"UPSTREAM_ERROR: …"}
# → the held amount is automatically refunded (see /v1/billing/transactions).Errors
All errors share one envelope:
{"error": {"code": "INSUFFICIENT_BALANCE", "message": "Balance 0.1200 USD cannot cover hold 0.6000 USD"}}| Code | Error | Description |
|---|---|---|
| 400 | INVALID_INPUT | A field is missing or malformed. The message names the exact field. |
| 400 | INVALID_MODEL | The requested model id does not exist. See GET /v1/models for valid ids. |
| 400 | MODEL_RETIRED | The model has been retired and no longer accepts new tasks. Use its successor. |
| 401 | UNAUTHORIZED | Missing credentials. Generation endpoints need an API key; console endpoints need a JWT. |
| 401 | INVALID_API_KEY | The API key is wrong, revoked, or malformed. |
| 401 | INVALID_CREDENTIALS | Email/password sign-in failed. |
| 401 | KEY_DISABLED | The API key exists but has been disabled. |
| 402 | INSUFFICIENT_BALANCE | Your balance cannot cover the hold for this request. Top up to continue. |
| 403 | FORBIDDEN | Authenticated, but not allowed (e.g. a non-admin account calling an admin endpoint). |
| 404 | NOT_FOUND | The resource (task, key, etc.) does not exist or does not belong to you. |
| 409 | EMAIL_EXISTS | That email is already registered. Sign in instead. |
| 429 | RATE_LIMITED | Too many requests — slow down and retry. |
| 429 | MONTHLY_LIMIT_EXCEEDED | This API key has reached its monthly spending limit. |
| 502 | UPSTREAM_ERROR | The upstream provider failed. The held amount is auto-refunded; safe to retry. |
| 503 | PROVIDER_NOT_READY | The model is temporarily unavailable (e.g. pending funding). Try another model. |
Rate Limits
Exceeding a limit returns 429. Back off for a few seconds and retry. You can set a monthly spending cap on each API key in Console → API Keys; once the cap is hit, generation calls return 429 MONTHLY_LIMIT_EXCEEDED until the next month.
SDKs
The API is plain REST + JSON — any HTTP client works. Official Python and Node SDKs are in development; until then, the snippets below are production-ready starting points.
import time, requests
API = "https://api.viddra.com"
H = {"Authorization": "Bearer " + "YOUR_VIDDRA_API_KEY"}
task = requests.post(API + "/v1/video/generations",
headers=H, json={"model": "wan2.6", "prompt": "A cat DJing at a club"}).json()
while True:
t = requests.get(f"{API}/v1/video/generations/{task['id']}", headers=H).json()
if t["status"] == "succeeded":
print(t["video_url"]); break
if t["status"] == "failed":
raise RuntimeError(t.get("error")) # auto-refunded
time.sleep(5)const API = 'https://api.viddra.com';
const H = { Authorization: `Bearer ${process.env.VIDDRA_API_KEY}`,
'Content-Type': 'application/json' };
const task = await fetch(API + '/v1/image/generations', {
method: 'POST', headers: H,
body: JSON.stringify({ model: 'flux', prompt: 'Aurora over a fjord' })
}).then(r => r.json());
while (true) {
await new Promise(r => setTimeout(r, 5000));
const t = await fetch(`${API}/v1/image/generations/${task.id}`, { headers: H }).then(r => r.json());
if (t.status === 'succeeded') { console.log(t.result_url); break; }
if (t.status === 'failed') throw new Error(t.error); // auto-refunded
}