Skip to main content

Overview

Veo 3.1 official API forwarding is now available through api.laozhang.ai and is compatible with the OpenAI Videos API style. For new integrations, create a dedicated token, keep it in the default group, and set Billing mode to Pay-per-request. This page documents the API format, parameters, pricing, and code examples.

Pricing Advantage

Veo 3.1 official forwarding uses Pay-per-request billing with one unified price across supported duration and resolution combinations. Duration and resolution do not add extra charges. Based on public pricing from Google Gemini API Pricing, Google bills Veo 3.1 per second; the comparison below uses 8-second videos.
ModelLaoZhang priceGoogle 8s 1080pLower byGoogle 8s 4KLower by
veo-3.1-fast-generate-preview$0.3/call$0.9668.8%$2.4087.5%
veo-3.1-generate-preview$1.2/call$3.2062.5%$4.8075.0%
This route uses the same OpenAI Videos API style as Sora2 official forwarding:
StepMethodPathResponse
Create a video taskPOST/v1/videosJSON task object
Poll task statusGET/v1/videos/{id}JSON status object
Compatibility status pollingGET/v1/video/generations/{id}JSON status object
Download video outputGET/v1/videos/{id}/contentvideo/mp4 bytes
Use the OpenAI SDK for text-to-video and single first-frame image-to-video. Upload local image files with input_reference. For first/last-frame generation, use a JSON request body with images and metadata.lastFrame Data URIs. Do not treat multi-reference-image fields as production-ready capabilities yet.
On 2026-06-27, retesting with the upstream JSON example verified that images[] + metadata.lastFrame produces videos matching both the first and last frame. Both 720p and 1080p passed frame extraction checks. The field is case-sensitive: use lastFrame, not lastframe; the request body must be JSON, not multipart; pass duration as the string "8", not the number 8. metadata.referenceImages still returns referenceImage isn't supported by this model, so multi-reference images remain unavailable.
Do not use the legacy Veo-3.1 sync route, legacy Chat Completions examples, or old model names such as veo-3.1, veo-3.1-fast, or veo-3.1-fl. The official-forward route only uses the model names and /v1/videos task API documented here.

Token Setup

SettingValue
Console entryToken management → create a new token
GroupDefault group
Billing modePay-per-request
RecommendationCreate a dedicated Veo 3.1 official-forward token for easier usage review
Veo 3.1 official forwarding uses the Pay-per-request billing mode across supported duration and resolution combinations. Duration and resolution do not add extra charges.

Supported Models

ModelPrice per callDescriptionBest for
veo-3.1-fast-generate-preview$0.3Veo 3.1 Fast preview modelQuick tests, drafts, cost-sensitive workflows
veo-3.1-generate-preview$1.2Veo 3.1 standard preview modelHigher-quality outputs, production assets, complex shots

OpenAI SDK Quick Start

For text-to-video and single-image image-to-video, use the OpenAI SDK first. Set base_url to https://api.laozhang.ai/v1.
import time
from openai import OpenAI

client = OpenAI(
    api_key="YOUR_API_KEY",
    base_url="https://api.laozhang.ai/v1",
)

video = client.videos.create(
    model="veo-3.1-fast-generate-preview",
    prompt="A rainy neon street, a delivery robot driving through reflections, cinematic camera pan",
    seconds="4",
    size="1280x720",
)

video_id = video.id

while True:
    video = client.videos.retrieve(video_id)
    if video.status == "completed":
        break
    if video.status == "failed":
        raise RuntimeError(video.error)
    time.sleep(10)

content = client.videos.download_content(video_id)
content.write_to_file("veo-output.mp4")
For single-image image-to-video, pass the local file as input_reference:
with open("reference.png", "rb") as image_file:
    video = client.videos.create(
        model="veo-3.1-fast-generate-preview",
        prompt="Animate this reference image with a gentle camera push-in",
        seconds="4",
        size="1280x720",
        input_reference=image_file,
    )

Parameters

Core Parameters

ParameterTypeRequiredDescription
modelstringYesveo-3.1-fast-generate-preview or veo-3.1-generate-preview
promptstringYesVideo prompt
secondsstringNoDuration. For production integrations, use "8"
durationstringNoDuration. Pass "8" for both multipart and JSON first/last-frame requests; do not pass numeric 8
sizestringNoOutput size such as 1280x720, 720x1280, 1920x1080, 1080x1920, 3840x2160
resolutionstringNo720p, 1080p, or 4k
aspectRatiostringNo16:9 or 9:16
metadatastring / objectNoJSON string in multipart requests; object in JSON first/last-frame requests, such as {"lastFrame":"data:image/jpeg;base64,..."}
negativePromptstringNoNegative prompt, such as blurry, watermark, distorted. For image-to-video requests, omit it unless you have verified the behavior yourself
seedstringNoRandom seed for similar repeatability
input_referencefileNoVerified field for single first-frame image-to-video, uploaded as multipart/form-data
imagesstring[]NoFirst-frame Data URI array for JSON first/last-frame requests
metadata.lastFramestringNoLast-frame Data URI for JSON first/last-frame requests; field name is case-sensitive
referenceImages / metadata.referenceImagesfile / string[]NoCurrently rejected upstream with referenceImage unsupported; do not expose it
reference_image / reference_imagesfileNoCompatible multi-reference fields, currently not available
referenceType / reference_typestringNoReference image type field; do not rely on it alone
videofileNoVideo extension field. Upload an existing MP4 as the continuation reference
Do not pass generateAudio. Veo 3 / Veo 3.1 models have native audio, but this API does not support audio toggling through generateAudio. Passing it may return INVALID_ARGUMENT. To guide audio content, describe dialogue, ambient sound, effects, or music style in the prompt.

Duration and Resolution

Use caseSuggested parameters
4s landscape 720pseconds="4", size="1280x720", resolution="720p", aspectRatio="16:9"
8s landscape 720pseconds="8", size="1280x720", resolution="720p", aspectRatio="16:9"
8s landscape 1080pseconds="8", size="1920x1080", resolution="1080p", aspectRatio="16:9"
8s portrait 1080pseconds="8", size="1080x1920", resolution="1080p", aspectRatio="9:16"
8s landscape 4Kseconds="8", duration="8", size="3840x2160", resolution="4k", aspectRatio="16:9", metadata='{"durationSeconds":8,"resolution":"4k","aspectRatio":"16:9"}'
Pass seconds and duration as strings rather than numbers. Use 720p + 4s for quick tests. For production integrations, use 8-second requests. 1080p and 4k only support 8 seconds. For image-to-video, upload a local image file instead of a remote image URL.
Parameter limit: 1080p and 4k must be used with 8-second requests. Do not combine them with 4-second or 6-second requests. For 4K, also pass metadata.resolution="4k"; otherwise the downloaded file may be rendered as 1080p.
4K requests use the unified price. If you need to verify native 4K output, download the MP4 and inspect its media metadata. Do not rely only on create-task parameters for final file resolution.

Text-to-Video

Create a Task

curl -X POST "https://api.laozhang.ai/v1/videos" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F model="veo-3.1-fast-generate-preview" \
  -F prompt="A cinematic shot of a lighthouse at sunset, ocean waves hitting the rocks, stable slow camera push-in" \
  -F seconds="8" \
  -F duration="8" \
  -F size="1280x720" \
  -F resolution="720p" \
  -F aspectRatio="16:9" \
  -F 'metadata={"durationSeconds":8,"resolution":"720p","aspectRatio":"16:9"}' \
  -F negativePrompt="blurry, watermark, distorted, low quality" \
  -F seed="20260520"

Create Response

{
  "id": "task_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "task_id": "task_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "object": "video",
  "model": "veo-3.1-fast-generate-preview",
  "status": "queued",
  "progress": 0,
  "created_at": 1779283975
}

Image-to-Video

Image-to-video uses the same create endpoint. Single first-frame image-to-video uses multipart input_reference; first/last-frame generation uses JSON images[] + metadata.lastFrame. Do not use multipart last_frame / lastFrame file fields as a replacement for JSON metadata.lastFrame.
curl -X POST "https://api.laozhang.ai/v1/videos" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F model="veo-3.1-generate-preview" \
  -F prompt="Animate the reference image with a gentle camera push-in, subtle background parallax, cinematic lighting" \
  -F seconds="8" \
  -F duration="8" \
  -F size="1280x720" \
  -F resolution="720p" \
  -F aspectRatio="16:9" \
  -F 'metadata={"durationSeconds":8,"resolution":"720p","aspectRatio":"16:9"}' \
  -F seed="20260520" \
  -F input_reference="@reference.jpg;type=image/jpeg"

4K Landscape Image-to-Video

For 4K landscape image-to-video, upload a 16:9 reference image and pass both resolution="4k" and metadata.resolution="4k".
curl -X POST "https://api.laozhang.ai/v1/videos" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F model="veo-3.1-fast-generate-preview" \
  -F prompt="Animate the 4K reference image with a slow cinematic camera push-in, subtle parallax, crisp details, stable lighting" \
  -F seconds="8" \
  -F duration="8" \
  -F size="3840x2160" \
  -F resolution="4k" \
  -F aspectRatio="16:9" \
  -F 'metadata={"durationSeconds":8,"resolution":"4k","aspectRatio":"16:9"}' \
  -F seed="20260521" \
  -F input_reference="@reference-4k.jpg;type=image/jpeg"

First/Last-Frame Generation

First/last-frame generation must use a JSON request body. Pass the first frame as a Data URI inside images, and pass the last frame as a Data URI inside metadata.lastFrame. Pass duration as a string, such as "8", so OpenAI-compatible gateways do not reject the numeric type. In testing, size="1920x1080" and duration="8" returned a 1920x1080, 8-second MP4 with extracted first and final frames matching the inputs.
curl -X POST "https://api.laozhang.ai/v1/videos" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "veo-3.1-fast-generate-preview",
    "prompt": "Create an 8-second cinematic transition with smooth camera motion. Do not add captions or on-screen text.",
    "size": "1920x1080",
    "duration": "8",
    "images": ["data:image/jpeg;base64,BASE64_FIRST_FRAME"],
    "metadata": {
      "lastFrame": "data:image/jpeg;base64,BASE64_LAST_FRAME"
    }
  }'

Multi-Reference Images

Multi-reference images are currently unavailable. When sent as metadata.referenceImages following the upstream example, both Fast and Standard models return referenceImage isn't supported by this model / INVALID_ARGUMENT. Do not expose material reference images in production; use the JSON metadata.lastFrame path above for first/last-frame generation.

Video Extension

Video extension uses the video file field to upload an existing MP4. Use 8-second requests. This mode continues the style and content from the uploaded video according to your prompt; it does not guarantee frame-perfect stitching.
curl -X POST "https://api.laozhang.ai/v1/videos" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F model="veo-3.1-fast-generate-preview" \
  -F prompt="Extend the uploaded rainy city video into a continued shot with the same neon street style" \
  -F video="@source.mp4;type=video/mp4" \
  -F seconds="8" \
  -F duration="8" \
  -F size="1280x720" \
  -F resolution="720p" \
  -F aspectRatio="16:9" \
  -F 'metadata={"durationSeconds":8,"resolution":"720p","aspectRatio":"16:9"}'
Reference image dimensions should match the target size when possible. For example, use a 1280x720 image when size=1280x720. JPEG, PNG, and WebP are supported. For video extension, upload an MP4 file and use streaming downloads with retries for the result.

Poll Status

Save the id or task_id from the create response, then poll status.
curl "https://api.laozhang.ai/v1/videos/task_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Authorization: Bearer YOUR_API_KEY"
In progress:
{
  "id": "task_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "object": "video",
  "model": "veo-3.1-fast-generate-preview",
  "status": "in_progress",
  "progress": 50,
  "created_at": 1779283975
}
Completed:
{
  "id": "task_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "object": "video",
  "model": "veo-3.1-fast-generate-preview",
  "status": "completed",
  "progress": 100,
  "created_at": 1779283975,
  "completed_at": 1779284026
}

Status Values

StatusMeaning
queuedQueued
in_progressGenerating
completedCompleted and ready to download
failedFailed

Compatibility Status Polling

If existing code uses the older video generations polling path, use this compatibility endpoint:
curl "https://api.laozhang.ai/v1/video/generations/task_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Authorization: Bearer YOUR_API_KEY"
This endpoint returns a task status object:
{
  "id": "task_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "object": "video",
  "model": "veo-3.1-fast-generate-preview",
  "status": "completed",
  "progress": 100,
  "created_at": 1779283975,
  "completed_at": 1779284026
}
The compatibility polling endpoint does not return a separate public video URL. Download video results through /v1/videos/{id}/content.

Download Video

After the task completes, use /content to retrieve MP4 bytes. The API returns video file bytes, not a public video URL.
curl -L "https://api.laozhang.ai/v1/videos/task_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/content" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -o veo-3-1-output.mp4
The video file may lag briefly after GET /v1/videos/{id} returns completed. If the content endpoint returns task status is IN_PROGRESS, a 400 JSON response, or a short-lived stream interruption, wait 10-20 seconds and retry. For 4K outputs, use streaming downloads and retries.

Python Complete Example

import base64
import json
import mimetypes
from pathlib import Path
import time

import requests

API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.laozhang.ai"

HEADERS = {
    "Authorization": f"Bearer {API_KEY}"
}


def video_metadata(seconds, resolution, aspect_ratio):
    payload = {
        "durationSeconds": int(seconds),
        "resolution": resolution,
        "aspectRatio": aspect_ratio,
    }
    return json.dumps(payload)


def form_items(fields):
    return [(key, (None, value)) for key, value in fields.items()]


def data_uri(image_path):
    mime_type = mimetypes.guess_type(image_path)[0] or "image/jpeg"
    with open(image_path, "rb") as image_file:
        encoded = base64.b64encode(image_file.read()).decode("ascii")
    return f"data:{mime_type};base64,{encoded}"


def create_text_video(
    prompt,
    model="veo-3.1-fast-generate-preview",
    seconds="8",
    size="1280x720",
    resolution="720p",
    aspect_ratio="16:9",
):
    fields = {
        "model": model,
        "prompt": prompt,
        "seconds": seconds,
        "duration": seconds,
        "size": size,
        "resolution": resolution,
        "aspectRatio": aspect_ratio,
        "metadata": video_metadata(seconds, resolution, aspect_ratio),
        "negativePrompt": "blurry, watermark, distorted, low quality",
    }
    response = requests.post(
        f"{BASE_URL}/v1/videos",
        headers=HEADERS,
        files={key: (None, value) for key, value in fields.items()},
        timeout=180,
    )
    response.raise_for_status()
    return response.json()


def create_image_video(
    prompt,
    image_path,
    model="veo-3.1-generate-preview",
    seconds="8",
    size="1280x720",
    resolution="720p",
    aspect_ratio="16:9",
):
    fields = {
        "model": model,
        "prompt": prompt,
        "seconds": seconds,
        "duration": seconds,
        "size": size,
        "resolution": resolution,
        "aspectRatio": aspect_ratio,
        "metadata": video_metadata(seconds, resolution, aspect_ratio),
    }
    with open(image_path, "rb") as image_file:
        files = form_items(fields)
        files.append(("input_reference", (Path(image_path).name, image_file, "image/jpeg")))
        response = requests.post(
            f"{BASE_URL}/v1/videos",
            headers=HEADERS,
            files=files,
            timeout=180,
        )
    response.raise_for_status()
    return response.json()


def create_first_last_frame_video(
    prompt,
    first_frame_path,
    last_frame_path,
    model="veo-3.1-fast-generate-preview",
    size="1920x1080",
):
    payload = {
        "model": model,
        "prompt": prompt,
        "size": size,
        "duration": "8",
        "images": [data_uri(first_frame_path)],
        "metadata": {
            "lastFrame": data_uri(last_frame_path),
        },
    }
    response = requests.post(
        f"{BASE_URL}/v1/videos",
        headers={**HEADERS, "Content-Type": "application/json"},
        json=payload,
        timeout=180,
    )
    response.raise_for_status()
    return response.json()


def create_video_extension(
    prompt,
    video_path,
    model="veo-3.1-fast-generate-preview",
):
    fields = {
        "model": model,
        "prompt": prompt,
        "seconds": "8",
        "duration": "8",
        "size": "1280x720",
        "resolution": "720p",
        "aspectRatio": "16:9",
        "metadata": video_metadata("8", "720p", "16:9"),
    }
    with open(video_path, "rb") as video_file:
        files = form_items(fields)
        files.append(("video", (Path(video_path).name, video_file, "video/mp4")))
        response = requests.post(
            f"{BASE_URL}/v1/videos",
            headers=HEADERS,
            files=files,
            timeout=180,
        )
    response.raise_for_status()
    return response.json()


def wait_for_video(task_id, timeout=900, interval=15):
    start = time.time()
    while time.time() - start < timeout:
        response = requests.get(
            f"{BASE_URL}/v1/videos/{task_id}",
            headers=HEADERS,
            timeout=60,
        )
        response.raise_for_status()
        payload = response.json()

        status = payload.get("status")
        progress = payload.get("progress", 0)
        print(f"status={status}, progress={progress}")

        if status == "completed":
            return payload
        if status == "failed":
            raise RuntimeError(f"video generation failed: {payload}")

        time.sleep(interval)

    raise TimeoutError("video generation timed out")


def download_video(task_id, output_path, retries=8, interval=10):
    last_error = None
    for _ in range(retries):
        response = requests.get(
            f"{BASE_URL}/v1/videos/{task_id}/content",
            headers=HEADERS,
            stream=True,
            timeout=600,
        )
        if response.status_code == 200:
            with open(output_path, "wb") as output_file:
                for chunk in response.iter_content(chunk_size=1024 * 1024):
                    if chunk:
                        output_file.write(chunk)
            return output_path

        last_error = response.text
        if "IN_PROGRESS" not in response.text and "in_progress" not in response.text:
            response.raise_for_status()
        time.sleep(interval)

    raise RuntimeError(f"download failed: {last_error}")


if __name__ == "__main__":
    task = create_text_video(
        prompt="A cinematic lighthouse at sunset, ocean waves, slow camera push-in",
        model="veo-3.1-fast-generate-preview",
        seconds="8",
        size="1280x720",
    )
    task_id = task.get("id") or task.get("task_id")
    wait_for_video(task_id)
    download_video(task_id, "veo-output.mp4")

FAQ

/v1/videos/{id}/content returns video/mp4 bytes, not a public video URL. /v1/video/generations/{id} returns a task status object, not a separate public video URL. In production, download it server-side, store it in your own OSS/CDN, and return your own URL to users.
Use the default group for Veo 3.1 official forwarding. Create a dedicated token and set Billing mode to Pay-per-request for cleaner billing review.
Billing uses Pay-per-request, not separate duration or resolution pricing. veo-3.1-fast-generate-preview is $0.3/call, and veo-3.1-generate-preview is $1.2/call. For production integrations, use 8-second requests; 1080p and 4k require 8 seconds. For 4K, also pass metadata.resolution="4k".
Two verified image-control paths are available: single first-frame generation with multipart input_reference, and first/last-frame generation with JSON images[] + metadata.lastFrame. Do not expose metadata.referenceImages as a material-reference feature right now; upstream currently returns referenceImage unsupported.
Yes. Upload an MP4 through the video file field and use seconds="8" plus duration="8". Video extension continues style and content from the uploaded video according to your prompt; it does not guarantee frame-perfect stitching.
Do not pass generateAudio. Veo 3 / Veo 3.1 models have native audio, but this API does not support toggling audio with generateAudio; describe the desired audio in the prompt instead.
Not recommended. Veo 3.1 official forwarding should use the same /v1/videos task API style as Sora2 official forwarding.