Image-to-Video API: Animate Still Images with AI — 2026 Guide
You have a product photo or a user-uploaded portrait, and you want to add motion — a short 5–8 second clip that brings the still image to life for a social feed, a landing page hero, or an in-app preview. Until recently, doing this programmatically meant complex video production pipelines or custom model training. Image-to-video APIs let you animate a still image with a text prompt in a single API call, with results ready in under two minutes.
This guide covers the two image-to-video (i2v) models available on GenRelay as of August 2026 — Veo 3.1 (i2v) and Grok Imagine 1.5 — with a complete Python integration you can drop into a backend service.
What Does an Image-to-Video API Do?
An image-to-video API takes a reference image and a motion prompt as input and returns a video clip as output. The model uses the input image as the starting frame and synthesizes subsequent frames based on the motion described in the prompt. The result is a short MP4 where the first frame matches your input image exactly.
The request schema looks like this:
{
"model": "veo-3-1-lite",
"mode": "i2v",
"image_url": "https://your-cdn.com/product-photo.jpg",
"prompt": "The bottle slowly rotates 360 degrees on a white surface",
"duration": 8,
"resolution": "720p"
}
Because generation takes 15–90 seconds, the API returns a job ID immediately. You then poll a status endpoint until the video is ready.
Which Models Support Image-to-Video?
As of August 2026, GenRelay offers two i2v-capable models:
| Model | Pricing | Max duration | Best for |
|---|---|---|---|
| Veo 3.1 (i2v) | $0.060/s (720p) · $0.120/s (1080p) | 8s | High-fidelity motion, complex camera moves |
| Grok Imagine 1.5 | $0.022/s | ~8s | Cost-efficient animation, product & portrait shots |
Veo 3.1 (i2v) produces high-fidelity motion with accurate physics and strong subject preservation from the reference image. It handles complex motion descriptions — circular orbits, multi-axis rotations, realistic fluid motion — reliably. An 8-second clip at 720p costs 8 × $0.060 = $0.48.
Grok Imagine 1.5 performs well for moderate-complexity motion: gentle rotations, subtle camera pans, product float animations, portrait eye blinks. At $0.022/s, an 8-second clip costs $0.18 — roughly 62% less than Veo 3.1 at the same duration.
For product e-commerce or social media content where subtle motion is sufficient, Grok 1.5 is the cost-efficient default. For cinematic motion or scenes where physics accuracy matters, Veo 3.1 is the stronger choice. See the Veo 3.1 image-to-video guide for a deep-dive on Veo-specific parameters.
Step 1: Set Up Authentication
GenRelay uses Bearer token authentication. Store your key in an environment variable and reference it from code — never hardcode it in source files.
import os
import requests
API_KEY = os.environ["GENRELAY_API_KEY"]
BASE_URL = "https://genrelay.ai/v1"
def auth_headers() -> dict:
return {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
}
If you rotate your key in the GenRelay dashboard, only the environment variable value needs updating — no code changes.
Step 2: Prepare Your Reference Image
The reference image must meet these requirements:
- Format: JPEG or PNG (no WebP, GIF, or SVG)
- URL: publicly accessible over HTTPS; presigned URLs work only if they don't expire before generation completes (allow at least 5 minutes)
- Minimum size: 512px on the short side
- File size: under 10 MB
For best results, match the input image resolution to your target output resolution. If you're generating 1080p video, use a reference image that is at least 1920×1080 pixels.
If your image is in local storage, upload it to a CDN or S3 bucket with public-read access before calling the API.
Step 3: Submit the Image-to-Video Job
def submit_i2v_job(
image_url: str,
prompt: str,
model: str = "grok-imagine-1-5",
resolution: str = "720p",
duration: int = 8
) -> str:
payload = {
"model": model,
"mode": "i2v",
"image_url": image_url,
"prompt": prompt,
"duration": duration,
"resolution": resolution
}
resp = requests.post(
f"{BASE_URL}/videos/generations",
headers=auth_headers(),
json=payload
)
resp.raise_for_status()
return resp.json()["id"]
raise_for_status() will raise an HTTPError on 4xx/5xx responses. Catch requests.exceptions.HTTPError in your caller to surface the error message from the response body.
Step 4: Poll for Completion
Video generation is asynchronous. The job progresses through statuses: "queued" → "processing" → "completed" (or "failed").
import time
def wait_for_video(
job_id: str,
poll_interval: int = 5,
timeout: int = 300
) -> str:
deadline = time.time() + timeout
while time.time() < deadline:
resp = requests.get(
f"{BASE_URL}/videos/generations/{job_id}",
headers=auth_headers()
)
resp.raise_for_status()
result = resp.json()
status = result["status"]
if status == "completed":
return result["data"][0]["url"]
elif status == "failed":
raise RuntimeError(f"Generation failed: {result.get('error', 'unknown')}")
time.sleep(poll_interval)
raise TimeoutError(f"Job {job_id} did not complete within {timeout}s")
Typical generation times:
- Grok Imagine 1.5: 15–40 seconds
- Veo 3.1 (i2v): 30–90 seconds
A 5-second poll interval is appropriate for both models. Polling more frequently increases your request count without speeding up generation.
Full Working Integration
Combining the steps above into a single function:
import os, requests, time
API_KEY = os.environ["GENRELAY_API_KEY"]
BASE_URL = "https://genrelay.ai/v1"
def auth_headers():
return {"Authorization": f"Bearer {API_KEY}"}
def animate_image(
image_url: str,
prompt: str,
model: str = "grok-imagine-1-5",
resolution: str = "720p",
duration: int = 8
) -> str:
# Submit
resp = requests.post(
f"{BASE_URL}/videos/generations",
headers=auth_headers(),
json={
"model": model,
"mode": "i2v",
"image_url": image_url,
"prompt": prompt,
"duration": duration,
"resolution": resolution
}
)
resp.raise_for_status()
job_id = resp.json()["id"]
print(f"Submitted: {job_id}")
# Poll
deadline = time.time() + 300
while time.time() < deadline:
check = requests.get(
f"{BASE_URL}/videos/generations/{job_id}",
headers=auth_headers()
)
check.raise_for_status()
data = check.json()
if data["status"] == "completed":
video_url = data["data"][0]["url"]
print(f"Done: {video_url}")
return video_url
elif data["status"] == "failed":
raise RuntimeError(data.get("error", "unknown failure"))
time.sleep(5)
raise TimeoutError("Job timed out after 5 minutes")
# Example: animate a product photo
video_url = animate_image(
image_url="https://your-cdn.com/sneaker-product.jpg",
prompt="The sneaker slowly rotates left to right, revealing the side profile",
model="grok-imagine-1-5",
resolution="720p",
duration=6
)
Writing Effective Motion Prompts
The motion prompt describes what the model should animate starting from your reference frame. A few patterns that produce consistent results:
- Name the subject explicitly: "The sneaker rotates…" instead of "It rotates…" — the model has no prior context.
- Specify direction: "slowly pans left", "zooms out gently", "tilts down", "circular orbit around the product".
- Match motion to use case: for product shots, use subtle rotations or gentle float. For portraits, use soft eye movements or slight head turns. Over-specifying motion on a static subject often produces unnatural artifacts.
- Keep prompts under 100 words: both models handle short, precise prompts better than long, complex ones.
For Veo 3.1, camera motion descriptors work particularly well — "slow push in", "wide establishing pan left", "drone pull-back reveal" — because Veo 3.1 has explicit camera control capabilities.
Pricing Math: What Does i2v Cost at Scale?
Grok Imagine 1.5 at $0.022/s (6-second clips):
| Daily volume | Cost/day | Cost/month |
|---|---|---|
| 100 clips | $13.20 | $396 |
| 500 clips | $66.00 | $1,980 |
| 2,000 clips | $264.00 | $7,920 |
Veo 3.1 Lite at $0.060/s (720p, 8-second clips):
| Daily volume | Cost/day | Cost/month |
|---|---|---|
| 100 clips | $48.00 | $1,440 |
| 500 clips | $240.00 | $7,200 |
| 2,000 clips | $960.00 | $28,800 |
For user-triggered generation where output quality is the primary constraint (e.g., a premium feature in a SaaS product), Veo 3.1 is worth the premium. For batch automation or high-volume content pipelines where cost-per-clip is the primary constraint, Grok 1.5 is the practical default.
Frequently Asked Questions
Can I use a presigned S3 URL as the reference image?
Only if the URL remains valid for the full duration of the generation job. A safe threshold is 10 minutes. If your presigned URLs are shorter than that, upload to a public bucket or CDN instead.
What image formats are supported as input?
JPEG and PNG. Animated GIFs and WebP are not accepted as input. Output is always an MP4 video file.
What if my reference image is low resolution?
Both models can upscale from the input, but low-resolution inputs produce noticeably softer output. Minimum recommended input: 512px on the short side for 720p output, 1080px width for 1080p output.
Can I generate videos longer than 8 seconds?
As of August 2026, both Veo 3.1 i2v and Grok Imagine 1.5 support up to 8 seconds per generation. For longer sequences, you can chain generations — feed the last frame of one clip as the reference image for the next. See the image-to-video pipeline guide for a chaining pattern with async queuing.
How do I handle 429 rate limit errors?
Back off and retry after the interval specified in the Retry-After response header, or after 15–30 seconds if the header is absent. For high-volume workloads, implement an async job queue so you can cap concurrent submissions and prevent 429s before they occur. The image-to-video pipeline guide includes a full asyncio-based queue pattern with exponential backoff.