Veo 3.1 API Tutorial: Text-to-Video, Image-to-Video & Audio (2026)
You want to ship video generation in your app and you want to use the current Veo model — not the earlier Veo 3 Lite variant, but the 3.1 update that added image-to-video, reference-to-video, and native audio baked into the output clip.
This tutorial covers the full Veo 3.1 API surface available via GenRelay: authentication, a working text-to-video request, the image-to-video and reference-to-video modes, how audio generation works, async polling patterns for production, and billing math with real numbers.
What is Veo 3.1 and what's new compared to Veo 3?
Veo 3.1 is Google's current video generation model as of August 2026. Compared to the Veo 3 generation, it adds three capabilities: native audio output (background music, ambient sound, and dialogue synthesized alongside the video), image-to-video (animate a starting frame), and reference-to-video (use a reference image to guide visual style or character consistency without animating it).
Via GenRelay, you access Veo 3.1 at https://genrelay.ai/v1/videos/generations using a single Bearer token. No separate Google Cloud agreement or Vertex AI project required.
Veo 3.1 Lite pricing on GenRelay (as of August 2026):
| Resolution | Price per second of output |
|---|---|
| 720p | $0.060 |
| 1080p | $0.120 |
Billing is by the second of generated video, not by generation time. A 5-second 720p clip costs $0.30; the same duration at 1080p costs $0.60. Failed jobs are not charged.
How do I authenticate with the GenRelay API?
All GenRelay endpoints use Bearer token authentication. Get your API key from genrelay.ai and include it in every request header:
Authorization: Bearer grk_live_YOUR_API_KEY
New accounts receive free credits — enough for several test generations before you add payment.
How do I make a text-to-video request?
Veo 3.1 generation is asynchronous. The POST returns a job ID immediately; you poll for results using a separate GET request. Here is a complete Python implementation:
import requests
import time
API_KEY = "grk_live_YOUR_KEY_HERE"
BASE = "https://genrelay.ai"
def submit_video_job(payload: dict) -> str:
resp = requests.post(
f"{BASE}/v1/videos/generations",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
},
json=payload,
)
resp.raise_for_status()
job_id = resp.json()["id"]
print(f"Job submitted: {job_id}")
return job_id
def poll_job(job_id: str, interval: int = 5, timeout: int = 360) -> dict:
deadline = time.time() + timeout
while time.time() < deadline:
resp = requests.get(
f"{BASE}/v1/videos/generations/{job_id}",
headers={"Authorization": f"Bearer {API_KEY}"},
)
result = resp.json()
status = result.get("status")
print(f" status: {status}")
if status == "succeeded":
return result
if status == "failed":
raise RuntimeError(f"Job failed: {result.get('error')}")
time.sleep(interval)
raise TimeoutError(f"Job {job_id} timed out after {timeout}s")
# Text-to-video: 5s clip at 720p
job_id = submit_video_job({
"model": "veo-3.1",
"prompt": "A slow pan across a misty mountain valley at dawn, golden hour light, cinematic",
"duration": 5,
"resolution": "720p",
"aspect_ratio": "16:9",
})
result = poll_job(job_id)
print("Video URL:", result["output"]["url"])
# Cost: 5s × $0.060 = $0.30
curl equivalent:
# 1. Submit
curl -X POST https://genrelay.ai/v1/videos/generations \
-H "Authorization: Bearer grk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "veo-3.1",
"prompt": "A product shot rotating on a white pedestal, studio lighting",
"duration": 5,
"resolution": "720p",
"aspect_ratio": "16:9"
}'
# 2. Poll (replace JOB_ID with the id from the response above)
curl https://genrelay.ai/v1/videos/generations/JOB_ID \
-H "Authorization: Bearer grk_live_YOUR_KEY"
Supported aspect ratios: 16:9, 9:16, 1:1, 4:3. Duration range: 1–8 seconds per request.
How do I use Veo 3.1 for image-to-video?
Pass an image_url alongside your prompt. Veo 3.1 animates the provided image in the direction your prompt specifies — the image becomes the first frame and the model generates motion from it.
job_id = submit_video_job({
"model": "veo-3.1",
"prompt": "The sneaker slowly rotates to reveal the heel, soft studio lighting, white background",
"image_url": "https://your-cdn.com/sneaker-front.jpg",
"duration": 5,
"resolution": "720p",
"aspect_ratio": "1:1",
})
result = poll_job(job_id)
print("i2v URL:", result["output"]["url"])
The image_url must be publicly accessible. If your images are behind authentication, pre-sign the URL or proxy through a public CDN before passing it to the API. The image is used as the initial frame; its aspect ratio influences how the model fills the frame.
How does reference-to-video (ref2v) work?
Reference-to-video is a Veo 3.1 mode where a reference image guides the visual style, color palette, or character appearance of the generated video without animating it. The model generates original motion while inheriting visual characteristics from the reference.
This differs from image-to-video: in i2v, the image is the first frame. In ref2v, the image is a style anchor — the generated video is a new scene that looks consistent with the reference.
job_id = submit_video_job({
"model": "veo-3.1",
"mode": "ref2v",
"prompt": "A person walking through a forest path in autumn, same cinematic color grading",
"reference_image_url": "https://your-cdn.com/style-reference.jpg",
"duration": 5,
"resolution": "720p",
"aspect_ratio": "16:9",
})
result = poll_job(job_id)
print("ref2v URL:", result["output"]["url"])
i2v vs ref2v — when to use which:
| Image-to-video (i2v) | Reference-to-video (ref2v) | |
|---|---|---|
| Reference image becomes | First frame of the video | Style guide for new content |
| Motion generated from | The reference image | A new scene matching the style |
| Input field | image_url |
reference_image_url + "mode": "ref2v" |
| Best for | Product animation, portrait motion | Consistent look across multiple scenes |
How does Veo 3.1 native audio work?
Veo 3.1 can generate background music, ambient sound, and synthesized dialogue as part of the output — baked into the returned MP4 without a separate audio generation step. Enable it by adding "audio": true to your request:
job_id = submit_video_job({
"model": "veo-3.1",
"prompt": "A bustling street market at sunset, crowd chatter, ambient music, cinematic",
"duration": 8,
"resolution": "720p",
"aspect_ratio": "16:9",
"audio": True,
})
result = poll_job(job_id)
print("Audio+video URL:", result["output"]["url"])
Audio generation adds compute cost on top of the base per-second rate. Check the GenRelay pricing page for the current audio-enabled rate. Output is always MP4 (H.264 video, AAC audio). Download and store within 24 hours — hosted URLs expire after that window.
How do I calculate the cost of a Veo 3.1 job?
Billing is per second of output video at the resolution you specified. Use this table for quick estimates:
| Scenario | Duration | Resolution | Cost |
|---|---|---|---|
| Short social clip | 5s | 720p | 5 × $0.060 = $0.30 |
| Product demo | 8s | 720p | 8 × $0.060 = $0.48 |
| High-quality promo | 8s | 1080p | 8 × $0.120 = $0.96 |
| 20 product demos | 8s | 720p | 20 × $0.48 = $9.60 |
| 100 short clips/month | 5s | 720p | 100 × $0.30 = $30.00 |
For comparison: Grok Imagine 1.0 costs $0.010/s and Grok Imagine 1.5 costs $0.022/s — both are lower-cost options for less demanding quality requirements. See all GenRelay video models for the full comparison.
How do I handle errors and edge cases in production?
| HTTP status / job status | Cause | Action |
|---|---|---|
401 Unauthorized |
Invalid or missing API key | Check Authorization: Bearer header format |
402 Payment Required |
Credits exhausted | Add credits at genrelay.ai/billing |
422 Unprocessable Entity |
Invalid parameter (unsupported aspect ratio, duration out of range) | Validate parameters before submitting |
429 Too Many Requests |
Rate limit hit | Exponential backoff; minimum 5s between retries |
Job status: "failed" |
Model-side or content policy rejection | Retry once; check error.message for content policy clues |
Max duration per request is 8 seconds. For longer content, generate sequential clips and concatenate using ffmpeg:
ffmpeg -f concat -safe 0 -i clips.txt -c copy output.mp4
Where clips.txt lists each generated MP4 in order.
FAQ
Does the Veo 3.1 API support webhooks instead of polling?
Not yet as of August 2026. Poll GET /v1/videos/generations/{id} every 5–10 seconds. Typical generation time is 30–120 seconds depending on duration and resolution. Implement a timeout of at least 5 minutes before treating a job as failed.
What is the difference between model: "veo-3.1" and model: "veo-3"?
veo-3.1 routes to the current variant with i2v, ref2v, and audio support. veo-3 routes to an earlier variant without those modes. Use veo-3.1 for all new integrations — the Veo 3 API guide covers both for backward-compatibility context.
Can I submit multiple jobs in parallel?
Yes. Each POST request is independent and returns its own job ID. Submit concurrently and poll each job separately. Your rate limit applies to concurrent submissions — check your plan tier in the GenRelay dashboard.
Does 720p vs 1080p affect how long generation takes?
Slightly — 1080p jobs typically take 15–30% longer to generate, independent of the price difference. For latency-sensitive product flows where a user is waiting, 720p reduces both cost and perceived generation time.
What content is blocked at the API layer?
Explicit content, realistic depictions of real individuals in fabricated scenarios, and content that violates Google's Veo usage policies are rejected at submission with a 400 response and a reason field. Rejected requests are not charged.
Next steps
- Review the Veo 3 API guide for a broader model overview including the Standard and Standard+Audio tier pricing
- If your pipeline generates still images alongside video, compare Nano Banana Pro vs Nano Banana 2 — both are on the same GenRelay platform
- See all GenRelay video models: Veo 3.1, Gemini Omni Flash, Grok Imagine 1.0, Grok Imagine 1.5, with per-model pricing