Veo 3.1 Video Generation
Use EasyRouter's unified API to call Google Veo 3.1 video generation models — text-to-video and image-to-video, with optional synchronized audio generation.
Veo 3.1 is Google's latest-generation video creation model, supporting text and image inputs, up to 4K resolution output, and native synchronized speech and sound effect generation. EasyRouter exposes it through the unified /v1/video/generations async task endpoint.
Veo 3.1 uses an async task-based API: submit a request, receive a task_id, poll for status, then download the video from data.url.
1. Supported Models
| Model ID | Version | Strengths | Best For |
|---|---|---|---|
veo-3.1-generate-001 | Veo 3.1 | Highest quality, 4K support, native audio | High-quality creative content, commercial production |
veo-3.1-fast-generate-001 | Veo 3.1 Fast | Faster speed, balanced quality | Rapid iteration, batch generation |
veo-3.1-lite-generate-001 | Veo 3.1 Lite | Fastest speed, lowest cost | Prototyping, high-volume testing |
All 3 models share the same endpoint, differentiated only by the model field. All support text-to-video, image-to-video, and optional audio generation.
2. Common Endpoints
2.1 Submit Task
POST /v1/video/generationsRequest Headers
| Header | Required | Description |
|---|---|---|
Authorization | ✓ | Bearer sk-YourApiKey |
Content-Type | ✓ | application/json |
Request Body Fields
| Field | Type | Required | Description |
|---|---|---|---|
model | string | ✓ | Veo 3.1 model ID (see table above) |
prompt | string | ✓ | Video description; English recommended for best results |
size | string | — | Resolution: 720p / 1080p / 4000 (4K, standard model only). Default: 1080p |
resolution | string | — | Same as size (recommended; aligns with upstream. When both are present, this field wins) |
duration | int | — | Video duration in seconds: 4 / 6 / 8. Default: 8 |
seconds | string | — | String form of duration (OpenAI Sora compatibility) |
aspect_ratio | string | — | Aspect ratio: 16:9 / 9:16. Default: 16:9 |
generate_audio | bool | — | Generate synchronized audio (speech, sound effects, ambient sound). Default: true (it is highly recommended to set this to false to save cost when you don't need audio) |
negative_prompt | string | — | Negative prompt describing content to exclude |
seed | int | — | Random seed (place inside metadata.seed, see below) |
image | string | — | Image-to-video first-frame image. Accepts HTTP(S) URL / data URI / raw base64 (see Image Input Formats) |
images | string[] | — | First-frame array; only images[0] is used. image takes precedence when both are provided |
last_frame_image | string | — | Last-frame image (combine with image / images[0] for first+last-frame interpolation, Veo 3.1 only). Same formats as image |
reference_images | string[] | — | Asset reference images (up to 3); forwarded as referenceType: "asset" upstream. Mutually exclusive with image / last_frame_image, only supported for duration=8 |
metadata | object | — | Container for Veo-native parameters, see Section 3 below |
OpenAI-style top-level fields are auto-mapped: snake_case top-level fields like generate_audio / negative_prompt / aspect_ratio are automatically forwarded as the corresponding camelCase parameters to upstream Veo.
You can also place them inside metadata (snake_case or camelCase both work) — equivalent behavior.
generate_audio: true roughly doubles the cost. The billing default is true, so if audio is not needed, explicitly set generate_audio: false to avoid being charged at the audio-included rate. See Section 7 for pricing details.
Response
{
"id": "task_DduhCcHShyasO6lCV7o5SK9JQEYUnO1j",
"task_id": "task_DduhCcHShyasO6lCV7o5SK9JQEYUnO1j",
"object": "video",
"model": "veo-3.1-generate-001",
"status": "queued",
"progress": 0,
"created_at": 1778590105
}2.2 Query Task Status
GET /v1/video/generations/{task_id}The response uses EasyRouter's unified {code, message, data} wrapper format.
Response (Completed / succeeded)
{
"code": "success",
"message": "",
"data": {
"task_id": "task_DduhCcHShyasO6lCV7o5SK9JQEYUnO1j",
"status": "succeeded",
"error": null,
"format": "mp4",
"metadata": null,
"url": "https://easyrouter.io/v1/videos/task_DduhCcHShyasO6lCV7o5SK9JQEYUnO1j/content"
}
}Response (Failed / failed)
{
"code": "success",
"message": "",
"data": {
"task_id": "task_DduhCcHShyasO6lCV7o5SK9JQEYUnO1j",
"status": "failed",
"error": "SafetyFilterActivated: prompt violates content policy",
"format": null,
"metadata": null,
"url": null
}
}Status Enum
| Status | Description |
|---|---|
queued | Submitted, waiting to start |
processing | Generating |
succeeded | Completed; data.url contains the video download URL |
failed | Failed; data.error contains details |
data Fields
| Field | Type | Description |
|---|---|---|
task_id | string | Task ID |
status | string | Task status |
error | string / null | Failure reason; null on success |
format | string / null | Output format (e.g., mp4); null while processing or on failure |
metadata | object / null | Additional metadata |
url | string / null | Video download URL; null while processing or on failure |
The video download URL (data.url) is a temporary signed link, valid for 24 hours. Download or transfer to your own storage immediately.
2.3 Image Input Formats
Both image and images[0] accept the following three formats. EasyRouter automatically detects and converts them into inline base64 (the format Veo's upstream API requires):
| Format | Example | Notes |
|---|---|---|
| HTTP(S) URL | https://example.com/foo.png | EasyRouter downloads the image server-side, base64-encodes it, and forwards. URL must be publicly reachable and not expired |
| data URI | data:image/png;base64,iVBOR... | Inline data passed through directly, no network fetch required. Useful when you don't want to expose the image URL |
| raw base64 | iVBORw0KGgo... (no prefix) | Plain base64 string; MIME type is auto-sniffed |
Limits:
- Max decoded image size: 20 MB
- Download timeout for URL form: 30 seconds
- Parse failure (URL unreachable / oversize / invalid base64) returns
400 parse images[0] failed: ...synchronously, no quota consumed
gs:// GCS native URIs are not supported. Use https://storage.googleapis.com/... HTTPS form instead.
3. metadata Advanced Parameters
metadata is the container for Veo-native parameters that don't have OpenAI-style equivalents. All top-level fields can also be placed inside metadata (snake_case or camelCase) to override defaults.
| Field | Type | Description |
|---|---|---|
seed | int | Random seed (recommended placement) |
personGeneration / person_generation | string | Person generation policy: allow_all / allow_adult / dont_allow |
storageUri / storage_uri | string | Write the result directly to a GCS bucket path (vertex channel only, requires permissions) |
compressionQuality / compression_quality | string | Compression quality: optimized (default) / lossless |
resizeMode / resize_mode | string | Image-to-video resize policy: crop / fit |
sampleCount / sample_count | int | Number of videos per request, currently fixed at 1, your value is overridden |
Precedence: camelCase in metadata > snake_case in metadata > top-level fields. In other words, when there's a name collision, metadata camelCase wins.
4. OpenAI-Compatible Endpoints (Optional)
In addition to /v1/video/generations, Veo 3.1 also supports OpenAI-style /v1/videos endpoints. Both are fully equivalent.
Submit: POST /v1/videos (identical request body fields)
Query: GET /v1/videos/{task_id} returns OpenAI flat format:
{
"id": "task_DduhCcHShyasO6lCV7o5SK9JQEYUnO1j",
"object": "video",
"model": "veo-3.1-generate-001",
"status": "completed",
"progress": 100,
"created_at": 1778590105,
"completed_at": 1778590201
}The OpenAI-compatible query endpoint does not return the video URL. Use /v1/video/generations/{task_id} and get the download URL from data.url.
5. Full Request Examples
# Step 1: Submit task
curl -X POST https://easyrouter.io/v1/video/generations \
-H "Authorization: Bearer sk-YourApiKey" \
-H "Content-Type: application/json" \
-d '{
"model": "veo-3.1-generate-001",
"prompt": "A golden retriever playing fetch on a sunny beach, cinematic wide shot, slow motion",
"size": "1080p",
"duration": 8,
"aspect_ratio": "16:9"
}'
# Step 2: Poll (recommended interval: 10~15 seconds)
curl https://easyrouter.io/v1/video/generations/task_DduhCcHShyasO6lCV7o5SK9JQEYUnO1j \
-H "Authorization: Bearer sk-YourApiKey"
# Step 3: When data.status == "succeeded", download from data.url
curl -o output.mp4 "https://easyrouter.io/v1/videos/task_DduhCcHShyasO6lCV7o5SK9JQEYUnO1j/content"# Enable generate_audio: true for synchronized speech, sound effects, and ambient sound
curl -X POST https://easyrouter.io/v1/video/generations \
-H "Authorization: Bearer sk-YourApiKey" \
-H "Content-Type: application/json" \
-d '{
"model": "veo-3.1-generate-001",
"prompt": "A jazz musician performing on a rainy street at night, neon lights reflecting on wet pavement",
"size": "1080p",
"duration": 8,
"generate_audio": true
}'Enabling audio roughly doubles the cost. Veo 3.1 standard, 1080p, 8s with audio = 1,360,000 quota.
# Pass a reference image via images
# The model generates motion starting from this image
curl -X POST https://easyrouter.io/v1/video/generations \
-H "Authorization: Bearer sk-YourApiKey" \
-H "Content-Type: application/json" \
-d '{
"model": "veo-3.1-generate-001",
"prompt": "The cat slowly opens its eyes and stretches, sunlight streaming through the window",
"images": ["https://example.com/cat.jpg"],
"size": "1080p",
"duration": 8,
"aspect_ratio": "16:9"
}'# 9:16 portrait i2v + synchronized audio — common e-commerce short-form video case
curl -X POST https://easyrouter.io/v1/video/generations \
-H "Authorization: Bearer sk-YourApiKey" \
-H "Content-Type: application/json" \
-d '{
"model": "veo-3.1-fast-generate-001",
"prompt": "Reference image constraint: use images[0] as the exact first frame. Preserve subject, color, logo and composition. Animate natural product motion only.",
"images": ["https://your-bucket.example.com/product.png"],
"size": "1080p",
"duration": 6,
"aspect_ratio": "9:16",
"generate_audio": true
}'- Image URL must be publicly reachable and not expired. For private buckets, use a pre-signed URL — otherwise you'll get
400 parse images[0] failed: download image returned http 403. aspect_ratio: "9:16"must be set explicitly; otherwise the video defaults to 16:9 landscape regardless ofsize.
# Veo 3.1 only: pass first + last frame, the model interpolates the transition
curl -X POST https://easyrouter.io/v1/video/generations \
-H "Authorization: Bearer sk-YourApiKey" \
-H "Content-Type: application/json" \
-d '{
"model": "veo-3.1-generate-001",
"prompt": "Smooth cinematic transition between a serene garden and a bustling city.",
"image": "https://example.com/start.jpg",
"last_frame_image": "https://example.com/end.jpg",
"size": "1080p",
"duration": 8,
"aspect_ratio": "16:9"
}'- Supported only on Veo 3.1 series (
generate-001/fast/lite). - Both images are downloaded and base64-encoded server-side; URLs must be publicly reachable.
# referenceImages: use multiple reference images to keep subject/scene consistent
# (mutually exclusive with image / last_frame_image)
curl -X POST https://easyrouter.io/v1/video/generations \
-H "Authorization: Bearer sk-YourApiKey" \
-H "Content-Type: application/json" \
-d '{
"model": "veo-3.1-generate-001",
"prompt": "The same character walks through a futuristic neon-lit city at night, cinematic.",
"reference_images": [
"https://example.com/character-front.png",
"https://example.com/character-side.png",
"https://example.com/scene-style.png"
],
"size": "1080p",
"duration": 8,
"aspect_ratio": "16:9"
}'- Mutual exclusion:
reference_imagescannot be used together withimage/images/last_frame_image. Mixing them returns400 reference_images is mutually exclusive .... - Up to 3 entries; more returns
400 reference_images supports at most 3 entries. - Only
duration=8is supported; other durations are rejected upstream. - Forwarded as
referenceType="asset". Veo 3.1 does not support thestylereference type.
6. Model Comparison
| Veo 3.1 | Veo 3.1 Fast | Veo 3.1 Lite | |
|---|---|---|---|
| Model ID | veo-3.1-generate-001 | veo-3.1-fast-generate-001 | veo-3.1-lite-generate-001 |
| Quality | Highest | Balanced | Basic |
| Speed (8s video, measured) | ~90–130s | ~75–105s | ~50–80s |
| Max Resolution | 4K | 1080p | 1080p |
| Audio | ✓ | ✓ | ✓ |
| Image-to-Video | ✓ | ✓ | ✓ |
7. Billing
Video Only (generate_audio: false)
| Model | 720p | 1080p | 4K |
|---|---|---|---|
veo-3.1-generate-001 | $0.20 / sec | $0.20 / sec | $0.40 / sec |
veo-3.1-fast-generate-001 | $0.08 / sec | $0.10 / sec | $0.25 / sec |
veo-3.1-lite-generate-001 | $0.03 / sec | $0.05 / sec | — |
Video + Audio (generate_audio: true)
| Model | 720p | 1080p | 4K |
|---|---|---|---|
veo-3.1-generate-001 | $0.40 / sec | $0.40 / sec | $0.60 / sec |
veo-3.1-fast-generate-001 | $0.10 / sec | $0.12 / sec | $0.30 / sec |
veo-3.1-lite-generate-001 | $0.05 / sec | $0.08 / sec | — |
Quota calculation formula:
quota = modelPrice × QuotaPerUnit × groupRatio × pricingRatio × seconds
QuotaPerUnit = 500,000
groupRatio = 1.0 (production default group)
Example (veo-3.1-generate-001, 1080p, 8s, with audio):
modelPrice = 0.20
pricingRatio = 2.0 (audio doubles the cost)
quota = 0.20 × 500,000 × 1 × 2.0 × 8 = 1,600,000
Example (veo-3.1-lite-generate-001, 1080p, 8s, video only):
modelPrice = 0.03
pricingRatio ≈ 1.667 (1080p multiplier)
quota = 0.03 × 500,000 × 1 × 1.667 × 8 ≈ 200,000On task failure (failed), EasyRouter automatically refunds quota to your account. No manual action required.
8. Polling Guide
| Item | Recommendation |
|---|---|
| Poll interval | Every 10–15 seconds; minimum 5 seconds |
| Terminal state | data.status == "succeeded" or data.status == "failed" |
| Total timeout | 5 minutes recommended |
| Typical duration (8s video, measured) | Lite: 50–80s; Fast: 75–105s; Standard: 90–130s |
| Video URL validity | 24 hours; download or transfer immediately |
Simple Polling Script (Bash)
#!/bin/bash
TID="task_DduhCcHShyasO6lCV7o5SK9JQEYUnO1j"
KEY="sk-YourApiKey"
while true; do
RESP=$(curl -sS "https://easyrouter.io/v1/video/generations/$TID" \
-H "Authorization: Bearer $KEY")
echo "$RESP"
STATUS=$(echo "$RESP" | python3 -c \
"import sys,json;print(json.load(sys.stdin)['data'].get('status',''))")
case "$STATUS" in
succeeded|failed) break ;;
esac
sleep 15
done9. Error Handling
HTTP Errors
| Code | Cause | Action |
|---|---|---|
400 | Invalid parameters (missing prompt, unsupported duration, or image parsing failure: parse images[0] failed: ...) | Check request body; see FAQ for image-related errors |
401 | API Key invalid or expired | Check Authorization header |
403 | API Key lacks access to this model | Check token model allowlist |
402 | Insufficient balance | Recharge at EasyRouter console |
Task Failure (failed)
| Common Reason | Recommendation |
|---|---|
Safety policy violation (data.error contains safety review details) | Revise prompt; avoid sensitive content; use negative_prompt |
| Inaccessible image URL | Ensure images URLs are publicly accessible and not expired |
Unsupported duration value | Use 4, 6, or 8 only |
| 4K not supported by model | Only veo-3.1-generate-001 supports size: "4000" |