When developers first try the Veo API, the most common point of confusion is its asynchronous nature. Unlike text generation where response.text gives you results instantly, video generation takes anywhere from 30 seconds to several minutes. You need to poll — repeatedly checking whether the operation has finished.
Miss that detail, and you'll spend an afternoon wondering why your code returns nothing. This guide covers the real errors developers hit with the Veo API, what causes them, and how to fix each one.
Why Veo API Behaves Differently — The LRO Pattern
Before diving into errors, it helps to understand the architecture. Veo API uses the Long-Running Operations (LRO) pattern. When you send a generation request, you get back an operation object — not a video. You then poll that operation until it signals completion.
import google.genai as genai
import time
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
# Sending the request returns an operation, not a video
operation = client.models.generate_videos(
model="veo-3.0-generate-preview",
prompt="A serene mountain lake at golden hour, cinematic style",
config=genai.types.GenerateVideoConfig(
aspect_ratio="16:9",
number_of_videos=1,
),
)
print(f"Operation name: {operation.name}")
print(f"Done: {operation.done}") # → False (still processing)The operation's done field starts as False and only becomes True when generation completes. Your code needs to handle that waiting period.
Error #1 — No Polling Loop (Most Common Issue)
The majority of "my Veo API isn't returning videos" reports come from missing polling. Developers expect immediate results the way text generation works.
What goes wrong
# ❌ This fails — operation.result is None while processing
operation = client.models.generate_videos(...)
video_url = operation.result.generated_videos[0].video.uri # AttributeErrorThe correct implementation
import google.genai as genai
import time
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
operation = client.models.generate_videos(
model="veo-3.0-generate-preview",
prompt="A timelapse of city lights at night, aerial view",
config=genai.types.GenerateVideoConfig(
aspect_ratio="16:9",
number_of_videos=1,
duration_seconds=5,
),
)
# Poll until the operation finishes
max_wait = 300 # 5 minutes max
interval = 15 # Check every 15 seconds
elapsed = 0
while not operation.done and elapsed < max_wait:
print(f"Generating... ({elapsed}s elapsed)")
time.sleep(interval)
elapsed += interval
operation = client.operations.get(operation)
if not operation.done:
print("Timeout: video generation did not complete in time")
else:
for video in operation.result.generated_videos:
print(f"Video URI: {video.video.uri}")Keep polling intervals at 10–15 seconds minimum. Shorter intervals waste quota on unnecessary API calls.
Error #2 — Safety Filter Rejections
Veo's content safety filters are stricter than those for text generation. When a prompt is blocked, operation.error will contain the details, or generated_videos will be an empty list.
What rejection looks like
operation.error.code = 400
operation.error.message = "Video generation blocked by safety filters"
Prompts that get flagged unexpectedly
Descriptions of accidents, disasters, and collisions often trigger the filter even when intent is clearly artistic. Real person names are reliably blocked. Violence and graphic scenarios of any kind are off-limits.
# ❌ Likely to be blocked
# "A car crash in slow motion"
# "Tsunami wave hitting buildings"
# ✅ Reframe for artistic intent
# "A car speeding dramatically away, cinematic lighting"
# "Powerful ocean waves crashing on a rocky coastline"Always check for errors after polling completes:
if operation.done and operation.error:
print(f"Error code: {operation.error.code}")
print(f"Message: {operation.error.message}")
elif operation.done and not operation.result.generated_videos:
print("Empty result — likely blocked by safety filters")For a deeper look at safety filter behavior across the Gemini API, see Gemini API Safety Filter Blocks: Causes and Workarounds.
Error #3 — Wrong Model Name or Invalid Parameters
Veo API is strict about model identifiers. As of April 2026, the available models are:
veo-3.0-generate-preview— Veo 3 (full quality)veo-2.0-generate-001— Veo 2 (stable)veo-3.0-fast-generate-preview— Veo 3 Lite (cost-efficient)
# ❌ These all fail
model="veo-3"
model="veo3"
model="veo-3-preview"
# ✅ Correct
model="veo-3.0-generate-preview"Parameter boundaries also matter. duration_seconds for veo-3.0-generate-preview is 5–8 seconds. Values outside this range return validation errors. For aspect_ratio, only "16:9", "9:16", and "1:1" are accepted.
# ✅ Valid config
config = genai.types.GenerateVideoConfig(
aspect_ratio="16:9", # "16:9" / "9:16" / "1:1" only
number_of_videos=1, # 1–2 depending on model
duration_seconds=5, # Veo 3: 5–8 seconds
)Error #4 — Video Download Failures
After a successful generation, downloading the video can still go wrong.
The URI expires
The video.uri returned by Veo API is a time-limited signed URL. If you wait too long after generation completes, the URL becomes inaccessible. Download immediately after the operation finishes.
import requests
import os
for i, video in enumerate(operation.result.generated_videos):
video_uri = video.video.uri
# Authentication header is required
headers = {"Authorization": f"Bearer {client._api_key}"}
response = requests.get(video_uri, headers=headers)
if response.status_code == 200:
filename = f"output_video_{i}.mp4"
with open(filename, "wb") as f:
f.write(response.content)
print(f"✅ Saved: {filename} ({len(response.content) / 1024:.1f} KB)")
else:
print(f"❌ Download failed: HTTP {response.status_code}")If you're running in a temporary environment like Colab or Cloud Run, files disappear when the session ends. For anything production-grade, upload to Cloud Storage immediately after download.
Error #5 — Quota Exhaustion and Unexpected Costs
Veo API uses a separate quota from text and image generation. The free tier allows very few video generations, making it easy to exhaust quota during development.
ERROR: 429 Resource exhausted
Quota exceeded for quota metric 'video_generation_requests'
For full quota troubleshooting, see Gemini API Quota and 429 Error Troubleshooting Guide.
Veo 3's cost per generation is dramatically higher than text generation. During development, use the cost-efficient Veo 3 Lite API instead of the full model.
Cost-conscious development pattern
import os
IS_DEV = os.getenv("ENV", "dev") == "dev"
def generate_video(prompt: str) -> str | None:
"""Generate video — Lite in dev, full model in production."""
model = "veo-3.0-fast-generate-preview" if IS_DEV else "veo-3.0-generate-preview"
print(f"Using model: {model}")
operation = client.models.generate_videos(
model=model,
prompt=prompt,
config=genai.types.GenerateVideoConfig(
aspect_ratio="16:9",
number_of_videos=1,
duration_seconds=5,
),
)
max_wait = 180
interval = 15
elapsed = 0
while not operation.done and elapsed < max_wait:
time.sleep(interval)
elapsed += interval
operation = client.operations.get(operation)
if operation.done and operation.result.generated_videos:
return operation.result.generated_videos[0].video.uri
return NoneQuick Debugging Checklist
When Veo API isn't behaving as expected, work through this list:
- Is polling implemented? — Does your code wait for
operation.done == Truebefore accessing results? - Is the model name correct? — Check against the official documentation for the exact identifier
- Are parameters in range? — Verify
duration_secondsandaspect_ratiovalues - What does
operation.errorsay? — Check whether it'sNoneor contains an error message - Are you downloading immediately? — Signed URLs expire; don't delay the download step
- Have you checked quota? — Visit the Google Cloud Console quota page for your project
For deeper implementation details, the complete Veo 3 API guide is worth a read. Once polling and error handling are in place, Veo API becomes a remarkably capable tool. The goal for today: get one working video generation from end to end.