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.

1

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.

2

Make your first call

Submit a generation task. You get a task id back immediately (202 Accepted) while the model runs asynchronously.

BASH
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", …}
3

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.

BASH
# 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-xxxxxxxxxxxxxxxxx
warning

Security 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.

IDNameTypePriceNotes
wan2.6Wan 2.6Video$0.16/sec720p · $0.24/sec 1080p
kling3.0Kling 3.0Video$0.27/sec3–15s, optional audio
seedanceSeedance 1.0 ProVideo$0.13/secbudget tier
seedance2.0Seedance 2.0Video$0.47/sec480p–4K tiers
seedance2.5Seedance 2.5Video$0.68/secup to 30s
seedance2.0-miniSeedance 2.0 MiniVideo$0.15/sec480p $0.08/sec · 4-15s budget
veo3Veo 3.1Video$0.35/sec$0.60/sec with audio
veo3.1-fastVeo 3.1 FastVideo$0.24/secspeed-optimized
fluxFLUX.1 [dev]Image$0.05/imgopen-weights
flux2-proFLUX.2 ProImage$0.06/imgnext-gen detail
seedreamSeedream 4.0Image$0.05/imghi-res + text
qwen-imageQwen ImageImage$0.03/imgtypography
ideogram-3.0Ideogram 3.0Image$0.10/imgbest in-image text
hailuo-2.3Hailuo 2.3Video$0.0933/sec768P · $0.15/sec 1080P
hailuo-h3Hailuo H3Video$0.12/sec768P · $0.20/sec 2K
speech-02-hdSpeech 02 HDAudio$0.18/kcharstudio-grade TTS
speech-2.8-hdSpeech 2.8 HDAudio$0.18/kchar19 emotions · 40 langs
wan2.2Wan 2.2Videoretired→ wan2.6
kling2.1Kling 2.1Videoretired→ kling3.0

Retired models return MODEL_RETIRED (400) and no longer accept new tasks.

Video API

POST/v1/video/generations

Starts 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

JSON
{
  "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)

JSON
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

POST/v1/image/generations

Same async contract as video: submit, poll, download. Available models: flux, seedream, qwen-image.

Request Body

JSON
{
  "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)

JSON
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

POST/v1/audio/speech

Text-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.

BASH
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

GET/v1/video/generations/{id}

Retrieve task status with your API key (image tasks: /v1/image/generations/{id}). Poll every 3–5 seconds. Statuses: queuedrunningsucceeded / failed.

Response (succeeded)

JSON
{
  "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:

JSON
{"error": {"code": "INSUFFICIENT_BALANCE", "message": "Balance 0.1200 USD cannot cover hold 0.6000 USD"}}
CodeErrorDescription
400INVALID_INPUTA field is missing or malformed. The message names the exact field.
400INVALID_MODELThe requested model id does not exist. See GET /v1/models for valid ids.
400MODEL_RETIREDThe model has been retired and no longer accepts new tasks. Use its successor.
401UNAUTHORIZEDMissing credentials. Generation endpoints need an API key; console endpoints need a JWT.
401INVALID_API_KEYThe API key is wrong, revoked, or malformed.
401INVALID_CREDENTIALSEmail/password sign-in failed.
401KEY_DISABLEDThe API key exists but has been disabled.
402INSUFFICIENT_BALANCEYour balance cannot cover the hold for this request. Top up to continue.
403FORBIDDENAuthenticated, but not allowed (e.g. a non-admin account calling an admin endpoint).
404NOT_FOUNDThe resource (task, key, etc.) does not exist or does not belong to you.
409EMAIL_EXISTSThat email is already registered. Sign in instead.
429RATE_LIMITEDToo many requests — slow down and retry.
429MONTHLY_LIMIT_EXCEEDEDThis API key has reached its monthly spending limit.
502UPSTREAM_ERRORThe upstream provider failed. The held amount is auto-refunded; safe to retry.
503PROVIDER_NOT_READYThe model is temporarily unavailable (e.g. pending funding). Try another model.

Rate Limits

100
requests / minute / IP, global
5
auth attempts / minute / IP
$
optional monthly spend cap per API key

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.

PYTHON
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)
NODE.JS
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
}