Home › Guides › How to call Veo 3.1

Calling Veo 3.1, Fast and Lite from curl and Python

Updated 2026-10-02

Veo 3.1 is available through one endpoint, POST https://videorouter.sh/api/v1/videos, under three model ids: google/veo-3.1, google/veo-3.1-fast and google/veo-3.1-lite. You need a VideoRouter key, not a cloud project. This page covers the working request, the polling loop, host pinning, and the errors you should handle on day one.

Create a job

curl https://videorouter.sh/api/v1/videos \
  -H "Authorization: Bearer llmr_sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "google/veo-3.1-fast",
    "prompt": "a lighthouse beam sweeping across a foggy harbor at night",
    "duration_secs": 8,
    "aspect_ratio": "16:9",
    "resolution": "720p"
  }'

The response is a job, not a video: an id, status: "queued" and the provider that accepted it. The full cost is charged once here, from the requested duration, plus a 2% platform fee on video usage. Everything after this call is free.

Poll and download in Python

import time
import requests

BASE = "https://videorouter.sh/api/v1"
H = {"Authorization": "Bearer llmr_sk_live_...", "Content-Type": "application/json"}

def veo(prompt, model="google/veo-3.1-fast", **fields):
    r = requests.post(f"{BASE}/videos", headers=H,
                      json={"model": model, "prompt": prompt, **fields})
    if r.status_code == 429:
        time.sleep(int(r.headers.get("Retry-After", 5)))
        r = requests.post(f"{BASE}/videos", headers=H,
                          json={"model": model, "prompt": prompt, **fields})
    r.raise_for_status()
    job = r.json()

    deadline = time.time() + 900
    while job["status"] not in ("completed", "failed"):
        if time.time() > deadline:
            raise TimeoutError(f"{job['id']} still running; poll it, do not resubmit")
        time.sleep(5)
        job = requests.get(f"{BASE}/videos/{job['id']}", headers=H).json()

    if job["status"] == "failed":
        raise RuntimeError(job["error"])
    return job["data"][0]["url"]

print(veo("a lighthouse beam sweeping across a foggy harbor", duration_secs=8))

Status goes queued, in_progress, then completed or failed. On success the URL is at data[0].url; copy the file to your own storage rather than keeping that link. Jobs that fail upstream are not billed. Never resubmit a slow job: the second submission is a second charge, while polling is free.

Switching between Veo tiers

All three ids share one request shape, so promoting a prompt from draft to final is a string change: google/veo-3.1-lite for exploration, google/veo-3.1-fast for review, google/veo-3.1 for approved shots. Resolution support is not identical across tiers, and for Fast the resolution tier also changes the price, so look at each model page before hard-coding a tier. The cost strategy guide covers how to sequence them.

Choosing and pinning a host

Veo 3.1 is sold by several hosts, Google among them, and rates differ between them. Unpinned requests go to the cheapest healthy host, and a rejected submission falls through to the next one. For the flagship, the confirmed host suffixes in VideoRouter's documentation are google, pika, deepinfra, machgen, replicate and wavespeed; check the host table on each tier's model page for its own list.

"model": "google/veo-3.1-fast/replicate"

That suffix is a soft preference. To fail instead of falling back, send:

"provider": {"only": ["replicate"], "allow_fallbacks": false}

Pin when a host has a capability others lack, such as image input, or when you have a policy reason to use a named provider. Otherwise float and keep failover. Calling Google directly is not automatically the cheap option; compare before assuming:

ModelCheapest hostPriciest hostCheapest isHosts
google/veo-3.1 (2160p)Pika
$0.2 / second
MachGen
$0.6 / second
67% lower7
google/veo-3.1-fast (2160p)Replicate
$0.1 / second
MachGen
$0.3 / second
67% lower7
google/veo-3.1-lite (1080p)SandBase
$0.03 / second
WaveSpeedAI
$0.08 / second
62% lower3

Per second, before VideoRouter's 2% platform fee. For tiered models each row compares the resolution tier with the widest host-to-host gap. Built 2026-10-02 from the live catalog.

Handling errors

Every error uses the OpenAI-style envelope {"error": {"message", "type", "code"}} whichever host was involved.

StatusTypical causeResponse
400 invalid_request_errorMissing prompt, a typo in the model id, or a field the model or host does not acceptFix the request; do not retry
401 invalid_api_keyMissing, revoked or expired keyCheck the key
402Monthly key cap reached or prepaid balance exhaustedTop up or raise the cap
403 model_not_allowedThe tier is not in the key's allow-listUpdate the allow-list
429Per-key rate limitSleep for Retry-After and retry
5xx upstream_errorEvery candidate host failedRetry; not billed

There is a second failure channel: a job accepted successfully that later ends with status: "failed". Content-policy rejections are an ordinary outcome for prompts and user-supplied images, so show those to the user rather than retrying blindly. For a job that stalls after acceptance, failover.on_timeout_sec hedges to a second host, but both attempts are billed if both finish.

Testing without burning budget

Before you wire Veo into a product, run a small matrix of short requests: one per tier you plan to use, one with and one without your image, each with the resolution and aspect ratio you intend to ship. Keep the request body next to each job id and inspect each output. Because billing happens at creation, a ten-request test costs ten short clips, and a single misread default found now is cheaper than the same mistake across a batch. Give the key a monthly cap and a model allow-list while testing so a runaway loop cannot spend more than you intended.

Pitfalls worth testing for

Next, read the Veo 3.1 parameters page, browse the model pages for host tables, or create a key and run the curl above. The quickstart has the same flow end to end.

Frequently asked questions

What are the model ids for Veo 3.1?

google/veo-3.1, google/veo-3.1-fast and google/veo-3.1-lite. They share one request shape, so switching tiers means changing the model string.

Do I need a Google Cloud account?

No. A VideoRouter API key against https://videorouter.sh/api/v1/videos is enough. Google is one host among several the request can be routed to.

How do I pin Veo to a specific host?

Add a suffix such as google/veo-3.1-fast/replicate for a soft preference, or use provider.only with allow_fallbacks: false for a hard pin with no fallback.

Am I billed when a Veo job fails?

Jobs that fail upstream are not billed. A successful job is billed once at creation from the requested duration, and polling is free.

Keep reading

Using Veo is one part of the job.

VideoRouter puts it next to dozens of other video and image models behind one API key, so you can compare providers, prices and fail over automatically. Compare providers on VideoRouter →