GPT Proto
Schuyler Stacy2026-07-09

How to Generate Video With an AI API: Seedance 2.0, ViduQ3, and 3 More Through One Key

Step-by-step: generate video with an AI API in ~10 lines of Python. One key reaches Seedance 2.0, ViduQ3, Kling, Hailuo & Veo — runnable code, real pricing.

How to Generate Video With an AI API: Seedance 2.0, ViduQ3, and 3 More Through One Key

TL;DR: To generate video with an AI API, you POST a text prompt to a video model's endpoint, get back a job ID, then poll that job until the finished MP4 is ready — roughly ten lines of Python. GPTProto's unified API puts five video models behind one key and one balance: Seedance 2.0, ViduQ3-pro, Kling v3.0 std, Hailuo 2.3 Pro, and Veo 3.1. You switch between them by changing one string in the URL — the auth, the request shape, and the polling stay identical. Billing is prepaid pay-as-you-go, starting around $0.04 per second of video.

 

Calling five different video models used to mean five accounts, five SDKs, and five billing dashboards. Want ByteDance's Seedance 2.0? That's a BytePlus (Volcano Engine) account and an identity check most people outside China can't clear in an afternoon. Want Google's Veo? Different console, different key. Want to A/B two models on the same prompt? Now you're maintaining two integrations that share nothing.

This guide skips all of that. You'll make your first text-to-video API call in about ten lines of Python, poll for the finished MP4, then reach four more models by changing one string in the URL — same key, same code, same balance.

Full disclosure: I write these integration guides for GPTProto, so I have a stake in which endpoint you use. I've kept the numbers honest anyway, including the one model below where calling us costs more than going direct. When that's the case, I'll say so.

Table of contents

What an AI video generation API actually is

It's not a no-code button. It's an asynchronous job: you POST a prompt, the API hands back an ID, the model renders for anywhere from thirty seconds to several minutes, and you poll a second endpoint until the video is ready. That render delay is the whole reason for the two-step design — video takes too long to hold an HTTP connection open, so you get a job ticket instead of a file.

Three request shapes cover almost everything you'll build:

  • text-to-video — a prompt becomes a clip.
  • image-to-video — a still frame is animated forward.
  • reference-to-video — one or more reference images keep a character or object consistent across shots.
    Everything below uses the same base URL, https://gptproto.com/api/v3, and the same header. Let's get a key and make the first call.

Get your API key

Sign up at gptproto.com, open the API Keys section of the dashboard, and create a key. It looks like sk-.... There's no standing free tier here — billing is prepaid pay-as-you-go, so you top up a balance and every call draws from it starting with the first request. Budget a couple of dollars for testing; a handful of short clips won't cost more than that.

Install the one dependency:

pip install requests

One thing to get right before your first call, because it trips people up: on the native /api/v3/ surface, the Authorization header is your raw key with no Bearer prefix. GPT Proto also exposes an OpenAI-compatible /v1/ surface, and that one uses Bearer. Mixing them up is the most common 401 I see. For this whole tutorial we stay on the native surface, so the key goes in raw.

Your first call: Seedance 2.0, text-to-video

Seedance 2.0 is ByteDance's second-generation video model. It renders 4-to-15-second clips up to 1080p with native, synchronized audio, and it's built for multi-shot scenes where the camera cuts and the subject stays consistent. Here's a complete text-to-video submission:

import requests
import time
 
API_KEY = "sk-your-key-here"   # raw, no "Bearer" prefix
BASE = "https://gptproto.com/api/v3"
 
headers = {
    "Authorization": API_KEY,
    "Content-Type": "application/json",
}
 
def submit_seedance(prompt):
    url = f"{BASE}/bytedance/dreamina-seedance-2-0-260128/text-to-video"
    payload = {
        "prompt": prompt,
        "duration": 5,           # 4-15 seconds
        "aspect_ratio": "16:9",
        "resolution": "1080p",
        "generate_audio": True,
        "camera_fixed": False,
        "seed": -1,              # -1 = random
    }
    resp = requests.post(url, headers=headers, json=payload)
    resp.raise_for_status()
    return resp.json()["data"]["id"]
 
prompt = (
    "A lighthouse keeper climbs a narrow spiral staircase at dawn. "
    "Cut to a wide shot of the lamp room as the light sweeps across "
    "grey water. Waves crash below. Ambient wind and distant gulls."
)
job_id = submit_seedance(prompt)
print("submitted:", job_id)

The same request as cURL, if you'd rather see the wire format:

curl -X POST "https://gptproto.com/api/v3/bytedance/dreamina-seedance-2-0-260128/text-to-video" \
  -H "Authorization: sk-your-key-here" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "A lighthouse keeper climbs a spiral staircase at dawn, then the lamp sweeps across grey water.",
    "duration": 5,
    "aspect_ratio": "16:9",
    "resolution": "1080p",
    "generate_audio": true,
    "camera_fixed": false,
    "seed": -1
  }'

The response doesn't contain a video. It contains a job:

{
  "data": {
    "id": "cgt-20260709xxxxxx-abc",
    "status": "created",
    "outputs": [],
    "urls": { "get": "https://gptproto.com/api/v3/predictions/cgt-20260709xxxxxx-abc/result" }
  },
  "message": "success",
  "code": 200
}

data.id is your ticket. data.urls.get is a ready-made poll URL with the ID already baked in. In a nutshell: you submitted work, you got an ID, and nothing is rendered yet.

Poll for the result

Now you hit the result endpoint until status flips to completed, then read the video URL out of outputs. Every model on the platform uses this same poll endpoint and the same response shape, which is what makes the "swap one string" trick later work:

def wait_for_video(job_id, every=5, timeout=600):
    url = f"{BASE}/predictions/{job_id}/result"
    waited = 0
    while waited < timeout:
        resp = requests.get(url, headers=headers)
        resp.raise_for_status()
        data = resp.json()["data"]
        status = data["status"]
 
        if status == "completed":
            return data["outputs"][0]        # the MP4 URL
        if data.get("error"):
            raise RuntimeError(data["error"])
 
        print(f"  {status}... {waited}s")
        time.sleep(every)
        waited += every
 
    raise TimeoutError(f"timed out after {timeout}s (last status: {status})")
 
video_url = wait_for_video(job_id)
print("done:", video_url)

That's a full working generation. Two functions, one prompt, one MP4. A 5-second clip usually finishes in a minute or two, though a 15-second 1080p render with audio can take longer — poll every few seconds and don't hammer it.

A note on the loop: I raise on error and treat any non-completed, non-error status as "keep waiting." That's deliberate. Different models report intermediate states differently, and the safe move is to wait through anything that isn't a hard failure rather than hard-code a list of statuses that might change.

Swap one string, swap the engine: ViduQ3-pro

Here's the payoff. To render the same prompt on ViduQ3-pro instead of Seedance, you change the path — nothing else. The key, the headers, and wait_for_video() all stay exactly as they are:

def submit_vidu(prompt):
    url = f"{BASE}/vidu/viduq3-pro/text-to-video"
    payload = {
        "prompt": prompt,
        "duration": 5,           # ViduQ3 goes up to 16s
        "aspect_ratio": "16:9",
        "resolution": "1080p",
        "audio": True,           # note: ViduQ3 uses `audio`, not `generate_audio`
        "seed": -1,
    }
    resp = requests.post(url, headers=headers, json=payload)
    resp.raise_for_status()
    return resp.json()["data"]["id"]
 
job_id = submit_vidu(prompt)
video_url = wait_for_video(job_id)   # same poller, unchanged

ViduQ3-pro pushes clips to 16 seconds — twice Seedance's practical range — and it's the cheapest of the models here per second. The parameter names shift a little from model to model (ViduQ3 controls sound with audio; Seedance uses generate_audio), so check the model page for the exact field set, but the request/submit/poll rhythm never changes.

Keeping a character consistent. Both hero models do more than text-to-video. Point ViduQ3 at /vidu/viduq3-pro/image-to-video with an image field to animate a still, or use Seedance's reference-to-video mode with reference images so the same face carries across separate shots. It's the same submit-and-poll flow — you're adding an image input, not learning a new API. Confirm the reference-field names on each model page before you wire it into production.

Which one should you call?

Five models, one key. They aren't ranked tiers where a bigger number always wins — they're different jobs. Pick by what the clip needs:

Model Path ({provider}/{model}) Native audio Max length Price Reach for it when
Seedance 2.0 bytedance/dreamina-seedance-2-0-260128 Yes ~15s from $0.2957/run Multi-shot scenes with synced sound
ViduQ3-pro vidu/viduq3-pro Yes 16s from $0.04/s Longer clips, tightest price
Kling v3.0 std kling/kling-v3.0-std Yes ~10s from $0.2016/run Cheap, dependable image-to-video
Hailuo 2.3 Pro minimax/hailuo-2.3-pro No ~10s $0.441/run Clean 1080p when you'll add sound yourself
Veo 3.1 google/veo3.1 Yes from $0.5/run You need 4K output

Two things worth flagging before you commit. Hailuo 2.3 Pro returns silent video — great picture, but you're scoring it in post. And Veo 3.1 stamps every output with an embedded SynthID watermark you can't turn off, which matters if you're delivering to a client who doesn't want provenance marks baked in.

Because the platform normalizes all of these behind one submit/poll contract, iterating across them is a dictionary lookup, not a rewrite:

MODELS = {
    "seedance": "bytedance/dreamina-seedance-2-0-260128",
    "vidu":     "vidu/viduq3-pro",
    "kling":    "kling/kling-v3.0-std",
    "hailuo":   "minimax/hailuo-2.3-pro",
    "veo":      "google/veo3.1",
}
 
def submit(model_key, scene, payload):
    url = f"{BASE}/{MODELS[model_key]}/{scene}"
    resp = requests.post(url, headers=headers, json=payload)
    resp.raise_for_status()
    return resp.json()["data"]["id"]
 
# same wait_for_video() retrieves any of them
job_id = submit("kling", "image-to-video", {"prompt": prompt, "image": image_url})

Payloads differ per model — that's the part you tune — but discovery, auth, and retrieval don't.

What it actually costs

Video pricing is per second or per run, and it scales hard with resolution, duration, and whether you generate audio. The headline number on a model page is a floor, not a forecast. As a real example, a Seedance 720p / 16:9 / 5-second clip with audio comes out around $0.605 — well above its $0.2957 baseline — because you moved every cost lever at once. Price your actual settings from the dashboard estimate, not the sticker.

Here's the honest comparison against calling each vendor directly:

  • ViduQ3-pro — from $0.04/s, roughly 20% under the ~$0.05/s market reference.
  • Kling v3.0 std — from $0.2016/run, about 20% under the ~$0.252 reference.
  • Hailuo 2.3 Pro — $0.441/run against MiniMax's ~$0.49, about 10% cheaper.
  • Seedance 2.0 — from $0.2957/run, which is about 10% above rendering the same clip directly on ByteDance's own Dreamina. I'm not going to pretend otherwise. The reason to call it through the API isn't a lower price — it's stable programmatic access, one balance, and no BytePlus identity check. If cost is the only thing you care about for Seedance specifically, going direct is cheaper.
  • Veo 3.1 — from $0.5/run; there's no clean market reference on the page, so treat it as a convenience play, not a discount.
    There's no free tier and no monthly subscription — you pay per call from the first request. For pricing on any specific model, the model catalog shows the current per-second and per-resolution tables.

Errors and gotchas

A short field guide to what breaks:

  • 401 Unauthorized — almost always the Bearer mix-up. The native /api/v3/ surface wants the raw key.
  • 403 Forbidden — usually an empty balance, not a permissions problem. Top up.
  • 429 — you're rate-limited; back off and retry.
  • 400 with a content message — the prompt tripped moderation. Responses also carry a has_nsfw_contents flag worth checking in a pipeline.
  • Poll never completes — long renders exist; that's why wait_for_video() has a timeout instead of looping forever.
  • Wrong audio parametergenerate_audio (Seedance) vs audio (ViduQ3). Sending the wrong one silently does nothing rather than erroring, which is worse. Check the field names per model.

 

Start here

The fastest path: grab a key, run the Seedance and ViduQ3 snippets above, then swap the model string to try the rest. Everything you need to pick a model — current pricing, supported modes, resolution tables — is on each model page in the catalog. One key, one balance, five video engines.

FAQ

Do I need a separate account for each model?

No. One GPTProto key reaches all of these models on a single balance. You change the URL path, not the credentials.

How much does an AI video generation API cost?

It starts around $0.04 per second (ViduQ3-pro) and scales with resolution, duration, and audio. A 5-second 1080p clip with sound typically lands somewhere between $0.20 and $0.60 depending on the model.

Which AI video generation API is best in 2026?

It depends on the job. Seedance 2.0 for multi-shot scenes with synced audio, ViduQ3-pro for length and price, Kling v3.0 std for cheap image-to-video, Hailuo 2.3 Pro when you'll add your own soundtrack, Veo 3.1 when you need 4K. The comparison table above maps each to a use case.

Is there a free tier?

No. Billing is prepaid pay-as-you-go from the first call.

Can these APIs generate native audio?

Most here do — Seedance 2.0, ViduQ3-pro, Kling v3.0 std, and Veo 3.1 return synchronized sound. Hailuo 2.3 Pro returns silent video, so plan to add audio in post.