How to Integrate AI Video Generation into Your App — API Guide 2026

Aug 24, 2026·8 min read

Your product needs a "generate video" button. Users describe a scene; your backend turns it into an MP4. The implementation challenge isn't the API call itself — it's the async lifecycle: video generation takes 30–120 seconds, you need to poll for completion, handle failures, and retrieve the output before the hosted URL expires.

This guide covers the full integration path using the GenRelay API, which provides access to Veo 3.1, Gemini Omni Flash, Grok Imagine 1.0, and Grok Imagine 1.5 through one endpoint. The patterns here apply to all four models.

How Do You Authenticate with the Video Generation API?

Authentication uses a Bearer token on every request. Set it as an environment variable and attach it via an Authorization header.

import os
import requests

API_KEY = os.environ["GENRELAY_API_KEY"]
BASE_URL = "https://api.genrelay.ai/v1"

session = requests.Session()
session.headers.update({
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
})

Using a requests.Session lets you set auth once and reuse it across the job submission call and every polling request — relevant because you'll be making multiple requests per generation job.

Get your API key from the GenRelay console. Free credits are available on signup; paid plans start at $14/month with pay-as-you-go usage on top.

How Do You Submit a Video Generation Job?

Send a POST to /v1/videos/generations. The response is immediate and returns a job ID — not the video itself. The model processes the request asynchronously.

def submit_video_job(
    model: str,
    prompt: str,
    duration: int = 5,
    resolution: str = "720p",
) -> str:
    """Submit a video generation job. Returns the job ID string."""
    response = session.post(
        f"{BASE_URL}/videos/generations",
        json={
            "model": model,
            "prompt": prompt,
            "duration": duration,
            "resolution": resolution,
        },
        timeout=30,
    )
    response.raise_for_status()
    return response.json()["id"]

Core parameters:

Parameter Accepted values Notes
model veo-3-1-lite, omni-flash, grok-imagine-1-0, grok-imagine-1-5 Required
prompt string, max 1000 chars Describe scene, camera motion, lighting, style
duration integer (seconds) Veo 3.1 Lite: 5–8s; Grok: 4–6s; Omni Flash: fixed per billing tier
resolution 720p, 1080p Not all models support both — see model reference

Writing prompts for video differs from image prompts. Include camera instructions: "slow dolly-in", "static wide shot", "tracking shot from left". Motion descriptions ("a candle flame flickering", "water dripping") produce more consistent results than scene descriptions alone.

How Do You Poll for Job Completion?

After submission, poll the status endpoint at regular intervals until the job reaches completed or failed. The polling interval matters: too short wastes requests; too long delays your user's experience.

import time

def poll_video_job(
    job_id: str,
    interval: int = 10,
    timeout: int = 300,
) -> dict:
    """
    Poll until the job completes or the timeout is reached.
    Returns the completed job dict on success.
    Raises TimeoutError or RuntimeError on failure.
    """
    deadline = time.time() + timeout
    while time.time() < deadline:
        response = session.get(
            f"{BASE_URL}/videos/generations/{job_id}",
            timeout=15,
        )
        response.raise_for_status()
        job = response.json()

        status = job["status"]
        if status == "completed":
            return job
        if status == "failed":
            raise RuntimeError(
                f"Job {job_id} failed: {job.get('error', 'unknown error')}"
            )

        # In-progress — wait and try again
        time.sleep(interval)

    raise TimeoutError(f"Job {job_id} did not complete within {timeout}s")

Recommended polling configuration by model:

Model Typical completion (5s clip) Recommended interval Timeout
Veo 3.1 Lite (720p) 40–90s 10s 300s
Veo 3.1 Lite (1080p) 60–120s 15s 360s
Grok Imagine 1.0 20–60s 8s 180s
Grok Imagine 1.5 25–70s 10s 240s
Gemini Omni Flash 15–45s 8s 180s

For production systems, consider exponential backoff on polling if you're handling high job volumes. See Async video generation API: how to poll for results for advanced patterns including backoff and concurrent job management.

How Do You Retrieve and Store the Output?

When the job status is completed, the response includes a url pointing to the generated video file. Download it promptly — hosted URLs expire after a period that varies by model (typically 1–24 hours).

import urllib.request

def download_video(job: dict, output_path: str) -> str:
    """Download the completed video to output_path. Returns the path."""
    video_url = job["data"]["url"]
    urllib.request.urlretrieve(video_url, output_path)
    return output_path

Full flow, end to end:

job_id = submit_video_job(
    model="veo-3-1-lite",
    prompt="A barista pouring latte art in slow motion, close-up, warm café lighting",
    duration=5,
    resolution="720p",
)
print(f"Job submitted: {job_id}")

job = poll_video_job(job_id, interval=10, timeout=300)
path = download_video(job, f"/tmp/{job_id}.mp4")
print(f"Video saved: {path}")

In production, replace the download step with a direct upload to your object storage (S3, GCS, R2). Fetch the URL from the job response and stream it to your bucket without writing to local disk:

import boto3

def store_in_s3(job: dict, bucket: str, key: str) -> str:
    video_url = job["data"]["url"]
    s3 = boto3.client("s3")
    with requests.get(video_url, stream=True, timeout=60) as r:
        r.raise_for_status()
        s3.upload_fileobj(r.raw, bucket, key)
    return f"s3://{bucket}/{key}"

How Do You Handle Errors?

Three distinct error categories require different handling strategies.

401 — Invalid or expired key

try:
    response.raise_for_status()
except requests.HTTPError as e:
    if e.response.status_code == 401:
        raise ValueError(
            "API authentication failed — verify GENRELAY_API_KEY is set correctly"
        )
    raise

429 — Rate limited

Implement exponential backoff with jitter rather than a fixed retry:

import random

def submit_with_retry(model, prompt, duration, resolution, max_retries=3):
    for attempt in range(max_retries):
        try:
            return submit_video_job(model, prompt, duration, resolution)
        except requests.HTTPError as e:
            if e.response.status_code == 429 and attempt < max_retries - 1:
                wait = (2 ** attempt) + random.uniform(0, 1)
                time.sleep(wait)
                continue
            raise

Job-level failure

The failed status in the polling loop includes an error field with a provider-level message. Log the full job response before raising — it contains enough context to debug prompt rejections, content policy flags, and model capacity issues.

Which Video Model Should You Use?

Select based on clip quality needs and cost tolerance. As of August 2026:

Model Pricing Best suited for
Grok Imagine 1.0 $0.010/s Short clips, high-volume, budget-constrained
Grok Imagine 1.5 $0.022/s Better quality, supports image-to-video
Gemini Omni Flash $0.10/gen (720p) · $0.15/gen (1080p) Predictable per-generation cost
Veo 3.1 Lite $0.060/s (720p) · $0.120/s (1080p) High-quality text-to-video and image-to-video

Example cost for 1,000 × 5-second clips at 720p:

  • Grok 1.0: 1000 × (5 × $0.010) = $50
  • Omni Flash: 1000 × $0.10 = $100
  • Grok 1.5: 1000 × (5 × $0.022) = $110
  • Veo 3.1 Lite: 1000 × (5 × $0.060) = $300

Grok 1.0 is the most cost-effective for high-volume consumer-facing features. Veo 3.1 Lite produces noticeably higher quality output for marketing or showcase content where visual fidelity matters. For a detailed comparison including 1080p and longer durations, see Veo 3 API pricing.

How Do You Add Image-to-Video Support?

Veo 3.1 and Grok Imagine 1.5 both support image-to-video generation. Pass an image_url alongside your prompt — the image anchors the first frame; the prompt describes the motion from that starting point.

def submit_image_to_video_job(
    image_url: str,
    prompt: str,
    model: str = "veo-3-1-lite",
    duration: int = 5,
) -> str:
    response = session.post(
        f"{BASE_URL}/videos/generations",
        json={
            "model": model,
            "prompt": prompt,
            "image_url": image_url,
            "duration": duration,
            "resolution": "720p",
        },
        timeout=30,
    )
    response.raise_for_status()
    return response.json()["id"]

job_id = submit_image_to_video_job(
    image_url="https://your-bucket.s3.amazonaws.com/product-shot.jpg",
    prompt="The product slowly rotates, soft studio lighting, camera holds still",
    model="veo-3-1-lite",
)

The image must be publicly accessible — GenRelay fetches it server-side. Use a signed URL with sufficient TTL if the image is in private storage.

FAQ

Does GenRelay support webhook callbacks instead of polling?
As of August 2026, GenRelay uses polling-based async for video jobs. The status endpoint is lightweight, so polling at 8–15 second intervals is efficient for most workloads. Webhook support is on the roadmap.

What video formats does the API return?
Output is MP4 (H.264) for all current models. Resolution and bitrate vary by model and resolution tier.

Can I generate videos longer than 8 seconds?
Veo 3.1 Lite supports up to 8 seconds per request. Grok Imagine models generate clips in the 4–6 second range. Longer sequences require generating and concatenating multiple clips.

What's the rate limit?
Rate limits depend on your plan tier. Check the response headers (X-RateLimit-Limit, X-RateLimit-Remaining) on each request. If you hit 429 responses, use the backoff pattern shown above. Contact support to request higher limits for high-volume workloads.

How do I pick the right prompt for consistent results?
Specificity beats abstraction. Instead of "a busy street", write "rush-hour pedestrians crossing an intersection, Tokyo, overcast sky, wide establishing shot." Include camera movement ("slow push-in", "handheld follow") and lighting ("golden hour", "fluorescent overhead") for more consistent output across Veo 3.1 and Grok models.


The integration pattern for AI video generation is: submit a job, poll the status endpoint, download the output on completion. GenRelay normalizes this lifecycle across Veo 3.1, Grok Imagine, and Omni Flash under one endpoint. The Python snippets above form a working foundation — add error handling, retry logic, and your storage upload step to make them production-ready.

Related posts

Join our DiscordHow to Integrate AI Video Generation into Your App — API Guide 2026 — GenRelay