Animating a still with the Veo 3.1 API
Updated 2026-10-02
Image-to-video is the mode where you already have the first frame (a product shot, a character render, a storyboard panel) and want the model to supply motion. With the Veo 3.1 family the request is the standard video call plus one field. The catch is that image input is a per-host capability, so this guide spends as much time on checking that as on the happy path.
The request
Pass start_image_url, either a public https:// URL or an inline data:image/...;base64,... URI. The rest of the body is the same as text-to-video:
import requests
API = "https://videorouter.sh/api/v1"
HEADERS = {"Authorization": "Bearer llmr_sk_live_...", "Content-Type": "application/json"}
job = requests.post(
f"{API}/videos",
headers=HEADERS,
json={
"model": "google/veo-3.1-fast/replicate",
"prompt": "slow push-in on the mug, steam rising, soft morning light",
"start_image_url": "https://example.com/mug.jpg",
"duration_secs": 8,
"aspect_ratio": "16:9",
"resolution": "720p",
},
).json()
print(job["id"], job["status"])
Then poll GET /videos/{id} exactly as you would for text-to-video. The job goes queued, in_progress, completed or failed, polling is free, and the URL is in data[0].url.
If your image is local, upload it first with POST /v1/uploads (multipart, up to 50 MB). It returns a presigned URL that expires after 30 minutes, which is enough to pass straight into the next call but not somewhere to store the file.
Check host support before you build on it
Whether a Veo variant accepts an image depends on the host serving it. In the gateway's wiring, image input is available on a subset of hosts for the Veo family. At the time of writing that includes Replicate for Veo 3.1 and Fast and SandBase across the three variants, and one host's Lite listing exists only as an image-to-video endpoint. Direct Google and some other hosts are wired for text-to-video only. That list moves as hosts are added, so do not hard-code it from an article.
Practical consequences:
- Pin a host you have verified. Use the
model/hostsuffix, as in the example above, and run one cheap test call before you build a pipeline on it. - Read the model pages. The playground on each page shows which input fields the model accepts, and the host table shows who serves it.
- Expect a 400 when image input is not supported. The docs say models that do not take an image reject
start_image_urlwith a 400. Treat that as a signal to change host, not as a transient error to retry.
Remember that the model/host suffix is a soft preference. If you need the request to fail rather than fall back to a host with different behaviour, use "provider": {"only": ["replicate"], "allow_fallbacks": false}.
Writing the prompt for a still
The image already fixes subject, composition and lighting, so the prompt should describe change, not the scene. A workable structure:
- Camera move (slow push-in, locked-off, gentle orbit).
- Subject motion (what moves, how fast, in which direction).
- Environmental motion (steam, hair, foliage, reflections).
- Constraint (what must stay unchanged, such as the logo or the face).
Re-describing everything in the frame invites the model to reinvent it. Keep the prompt short and verb-heavy, and vary one element per test so you can tell what caused a change.
Aspect ratio, resolution and duration
- Match the source image. Send
aspect_ratiothat matches your still, because forcing a different ratio is an invitation to cropping or awkward extension. Accepted ratios per the docs are 16:9, 9:16, 1:1, 4:3, 3:4 and 21:9, but each model supports only some combinations. - Do not trust the fields blindly. An unsupported
resolutionoraspect_rationever returns a 400. It is ignored and the model default is used, and some Veo hosts ignore both. Probe the returned file withffprobein your tests. - Resolution is a price lever. For Veo 3.1 Fast the tier also changes the per-second price, so draft at the lowest tier you can judge motion on.
- Duration is what you pay for. The full cost is charged at creation from the requested
duration_secs. Ask for the shortest clip that proves the motion, and extend only after approval. Some reference-image modes pin duration, so check the model page.
Lite, then Fast, then full
Image-to-video has a convenient property: the first frame is fixed, so a cheap draft tells you most of what you need about motion. A sensible loop:
- Run the motion prompt on
google/veo-3.1-liteat a low resolution. You are checking whether the movement makes sense, not judging final fidelity. - Promote the prompts you like to
google/veo-3.1-fastand review at delivery resolution. - Render only the approved shots on
google/veo-3.1.
Two cautions. A cheaper tier is a preview of intent, not a pixel-exact preview of the final, so do not promise a client that the draft is what they will get. And because image support varies by host and variant, confirm each tier you plan to use accepts your image before relying on the ladder. The cost-strategy guide covers how to cap spend across the ladder.
Failure handling
- 400: unsupported field for this model or host, or an unreachable image URL. Fix the input.
- 402: prepaid balance exhausted or key spend cap reached.
- 5xx
upstream_error: every candidate host failed. These are not billed. - Job
failed: readjob["error"]. Content-policy rejections on the source image are a normal outcome for user-supplied photos, so surface them to the user rather than retrying.
Start with the quickstart if you have not made a first call yet, and grab a key at videorouter.sh/signup.
Frequently asked questions
How do I send an image to Veo 3.1?
Add start_image_url to the /videos request, as a public https URL or an inline base64 data URI. Image input is a per-host capability, so pin a host you have tested.
Do all Veo hosts support image-to-video?
No. Support is wired on a subset of hosts and variants, and models that do not take an image reject start_image_url with a 400. Check the model page and test one cheap call first.
What aspect ratio should I use?
Match your source image. Supported values are 16:9, 9:16, 1:1, 4:3, 3:4 and 21:9, but unsupported combinations are silently ignored, so verify the returned file.
Can I draft on Lite and finish on the full model?
Yes. All three variants share one request shape, so promoting a prompt is a change to the model string, provided the host you use accepts image input on each variant.
Keep reading
- Veo 3.1 vs Veo 3.1 Fast vs Lite — Which to Call from Your API
- Veo API Without Google Cloud: What You Skip and What You Keep
- Veo 3.1 Cost Control: Drafts, Caching and Tier Gating
- How to Call the Veo 3.1 API: curl, Python and Error Handling
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 →