Skip to main content
POST
/
v1
/
videos
Text-to-video: submit a generation task from text prompt
curl --request POST \
  --url https://api.apiyi.com/v1/videos \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "model": "veo-3.1-fast-generate-preview",
  "prompt": "A coastal lighthouse at dusk, slow push-in, waves lapping the rocks, distant seabirds, cinematic lighting, steady camera"
}
'
{
  "id": "task_xxxxxxxxxxxxxxxx",
  "task_id": "task_xxxxxxxxxxxxxxxx",
  "object": "video",
  "model": "veo-3.1-fast-generate-preview",
  "status": "queued",
  "progress": 0,
  "created_at": 1775025000,
  "completed_at": 1775025090
}
The interactive Playground on the right supports live debugging. Fill in your API Key under Authorization (format Bearer sk-xxx), enter prompt, pick model / seconds / metadata.resolution, then send. Default group works — no dedicated group switch needed.
Scope: This page covers “generate video from text only” — no input_reference, application/json body. To generate from a reference image, use the Image-to-Video endpoint (same endpoint + input_reference upload).
⚠️ Three most common pitfalls
  1. The length field is named seconds (not duration) and must be a string "4" / "6" / "8". Naming it duration is silently ignored → length falls back to the default 4 sec (the “sent 8s, got 4s” trap); passing a number fails with parse_request_failed: cannot unmarshal number into Go struct field ... duration of type string
  2. Do not pass generateAudio — upstream returns INVALID_ARGUMENT. Encode audio intent (ambient, dialogue, BGM) in the prompt instead
  3. At 1080p / 4k, seconds must be "8""4" / "6" will be rejected upstream
Three-step async flow — this page covers only Step 1 (submit)
  • Step 1 (this page): POST /v1/videos → returns task_id + status: "queued"
  • Step 2: GET /v1/videos/{task_id} poll until status: "completed"
  • Step 3: GET /v1/videos/{task_id}/content to download MP4
POST submit itself is sub-second and does not wait for generation. Full flow shown in the Python sample below.

Code Samples

Python (OpenAI SDK · low-level client.post)

{/* OpenAI SDK has no videos.create method; /v1/videos is a custom path, use low-level client.post() */}
from openai import OpenAI
import time

client = OpenAI(
    api_key="sk-your-api-key",
    base_url="https://api.apiyi.com/v1"
)

# Step 1: submit
resp = client.post(
    "/videos",
    body={
        "model": "veo-3.1-fast-generate-preview",
        "prompt": "A coastal lighthouse at dusk, slow push-in, waves lapping the rocks, distant seabirds, cinematic lighting, steady camera",
        "seconds": "8",  # string, not number
        "size": "1280x720",
        "metadata": {
            "resolution": "720p",
            "aspectRatio": "16:9",
            "seed": 20260521,
            "negativePrompt": "blurry, watermark, distorted, low quality"
        }
    },
    cast_to=dict
)
task_id = resp["task_id"]
print(f"Task ID: {task_id}, status: {resp['status']}")

# Step 2: poll (up to 3 minutes for 720p/1080p)
deadline = time.time() + 180
while time.time() < deadline:
    s = client.get(f"/videos/{task_id}", cast_to=dict)
    print(f"Status: {s['status']}, progress: {s.get('progress', 0)}%")
    if s["status"] == "completed":
        break
    if s["status"] == "failed":
        raise RuntimeError(f"Generation failed: {s}")
    time.sleep(8)

# Step 3: download (retry to handle CDN sync delay right after completed)
import urllib.request, urllib.error
time.sleep(4)
for i in range(5):
    try:
        req = urllib.request.Request(
            f"https://api.apiyi.com/v1/videos/{task_id}/content",
            headers={"Authorization": "Bearer sk-your-api-key"}
        )
        with urllib.request.urlopen(req, timeout=180) as r, open("output.mp4", "wb") as f:
            while chunk := r.read(1 << 16):
                f.write(chunk)
        break
    except urllib.error.HTTPError:
        if i == 4:
            raise
        time.sleep(4)
print("Saved: output.mp4")

Python (requests)

import requests
import time

API_KEY = "sk-your-api-key"
BASE_URL = "https://api.apiyi.com/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}

# Step 1: submit (JSON body)
resp = requests.post(
    f"{BASE_URL}/videos",
    headers={**HEADERS, "Content-Type": "application/json"},
    json={
        "model": "veo-3.1-fast-generate-preview",
        "prompt": "A coastal lighthouse at dusk, slow push-in, steady camera, ocean ambience and distant seabirds",
        "seconds": "8",  # must be a string
        "size": "1280x720",
        "metadata": {
            "resolution": "720p",
            "aspectRatio": "16:9"
        }
    },
    timeout=30  # POST is just an enqueue; 30 sec is enough
).json()
task_id = resp["task_id"]
print(f"Task ID: {task_id}, status: {resp['status']}")

# Step 2: poll (3 min for 720p/1080p, 10 min for 4K)
deadline = time.time() + 180
while time.time() < deadline:
    s = requests.get(f"{BASE_URL}/videos/{task_id}", headers=HEADERS).json()
    print(f"Status: {s['status']}, progress: {s.get('progress', 0)}%")
    if s["status"] == "completed":
        break
    if s["status"] == "failed":
        raise RuntimeError(f"Generation failed: {s}")
    time.sleep(8)

# Step 3: download with retry
time.sleep(4)
for i in range(5):
    try:
        with requests.get(
            f"{BASE_URL}/videos/{task_id}/content",
            headers=HEADERS, stream=True, timeout=180
        ) as r:
            r.raise_for_status()
            with open("output.mp4", "wb") as f:
                for chunk in r.iter_content(chunk_size=8192):
                    f.write(chunk)
        break
    except requests.HTTPError:
        if i == 4:
            raise
        time.sleep(4)
print("Saved: output.mp4")

cURL

{/* Just want to check/download with an existing task_id? See the "Already have a task_id?" section below */}
{/* Step 1: submit (the length field is seconds, the string "8", not number 8, and not duration) */}
RESP=$(curl -sS -X POST "https://api.apiyi.com/v1/videos" \
  -H "Authorization: Bearer sk-your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "veo-3.1-fast-generate-preview",
    "prompt": "A coastal lighthouse at dusk, slow push-in, ocean ambience, cinematic lighting",
    "seconds": "8",
    "size": "1280x720",
    "metadata": {"resolution": "720p", "aspectRatio": "16:9"}
  }')
TASK_ID=$(echo "$RESP" | python3 -c 'import sys,json;print(json.load(sys.stdin)["task_id"])')
echo "task_id=$TASK_ID"

{/* Step 2: poll every 8 sec */}
while :; do
  S=$(curl -sS -H "Authorization: Bearer sk-your-api-key" "https://api.apiyi.com/v1/videos/$TASK_ID")
  ST=$(echo "$S" | python3 -c 'import sys,json;print(json.load(sys.stdin)["status"])')
  echo "status=$ST"
  [ "$ST" = "completed" ] && break
  [ "$ST" = "failed" ] && { echo "$S"; exit 1; }
  sleep 8
done

{/* Step 3: download (--retry covers occasional 400 right after status=completed) */}
sleep 4
curl -sSL --retry 3 --retry-delay 4 \
  -H "Authorization: Bearer sk-your-api-key" \
  "https://api.apiyi.com/v1/videos/$TASK_ID/content" \
  -o output.mp4
ls -lh output.mp4

Node.js (native fetch)

import fs from 'node:fs';

const API_KEY = 'sk-your-api-key';
const BASE_URL = 'https://api.apiyi.com/v1';

// Step 1: submit
const submitResp = await fetch(`${BASE_URL}/videos`, {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${API_KEY}`
    },
    body: JSON.stringify({
        model: 'veo-3.1-fast-generate-preview',
        prompt: 'A golden retriever running on a sandy beach, slow motion, golden hour, cinematic quality',
        seconds: '8',  // must be string
        size: '1280x720',
        metadata: { resolution: '720p', aspectRatio: '16:9' }
    })
});
const { task_id } = await submitResp.json();
console.log(`Task ID: ${task_id}`);

// Step 2: poll
let status = 'queued';
while (status !== 'completed' && status !== 'failed') {
    await new Promise(r => setTimeout(r, 8000));
    const s = await (await fetch(`${BASE_URL}/videos/${task_id}`, {
        headers: { 'Authorization': `Bearer ${API_KEY}` }
    })).json();
    status = s.status;
    console.log(`Status: ${status}, progress: ${s.progress ?? 0}%`);
}

if (status === 'failed') throw new Error('Generation failed');

// Step 3: download (retry up to 3 times, 4-second gaps)
await new Promise(r => setTimeout(r, 4000));
let buffer;
for (let i = 0; i < 4; i++) {
    try {
        const resp = await fetch(`${BASE_URL}/videos/${task_id}/content`, {
            headers: { 'Authorization': `Bearer ${API_KEY}` }
        });
        if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
        buffer = Buffer.from(await resp.arrayBuffer());
        break;
    } catch (e) {
        if (i === 3) throw e;
        await new Promise(r => setTimeout(r, 4000));
    }
}
fs.writeFileSync('output.mp4', buffer);
console.log('Saved: output.mp4');

Browser JavaScript

{/* Demo only; route through a backend proxy in production to avoid Key leakage; large video downloads are also unsuitable for direct browser handling */}
const submitResp = await fetch('https://api.apiyi.com/v1/videos', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer sk-your-api-key'
    },
    body: JSON.stringify({
        model: 'veo-3.1-fast-generate-preview',
        prompt: 'Watercolor northern lights over snowy mountains, gentle motion',
        seconds: '4',
        size: '720x1280',
        metadata: { resolution: '720p', aspectRatio: '9:16' }
    })
});
const { task_id } = await submitResp.json();
console.log('Task ID:', task_id);

{/* After polling completes, route the /content endpoint through your backend proxy for download */}

Already have a task_id? Two copy-paste cURL commands

If you already have a task_id (returned when you submitted the task, or visible in your console logs), just replace two placeholders below and run:
  • sk-your-api-key → your APIYI key
  • task_xxxxxxxxxxxxxxxx → your task ID

1. Check task status

curl "https://api.apiyi.com/v1/videos/task_xxxxxxxxxxxxxxxx" \
  -H "Authorization: Bearer sk-your-api-key"
When the JSON response shows status: "completed", you can download; if it shows in_progress, wait a few seconds and check again.

2. Download the video (saved as output.mp4)

curl -L --retry 3 --retry-delay 4 \
  "https://api.apiyi.com/v1/videos/task_xxxxxxxxxxxxxxxx/content" \
  -H "Authorization: Bearer sk-your-api-key" \
  -o output.mp4
The /content endpoint requires the Authorization header — opening the URL directly in a browser address bar returns 401. --retry 3 covers the occasional 400 right after status flips to completed (CDN sync delay).

Parameter Reference

ParamTypeRequiredDefaultDescription
modelstringYesveo-3.1-fast-generate-preview ($0.3/req) or veo-3.1-generate-preview ($1.2/req)
promptstringYesVideo description; describe scene, action, camera, lighting, audio intent (do not pass generateAudio)
secondsstringNo"8"Video length, string enum: "4" / "6" / "8". The field is seconds, not duration (naming it duration is silently ignored, falls back to 4 sec). 1080p/4k must be "8"
sizestringNo1280x720Output pixels, e.g. 1280x720 / 1920x1080 / 3840x2160; lower precedence than metadata.resolution
metadata.resolutionstringNo720p720p / 1080p / 4k; higher precedence than size
metadata.aspectRatiostringNo16:916:9 (landscape) or 9:16 (portrait)
metadata.seedintNoRandom seed. Fixed seed clusters outputs in style (but cannot byte-reproduce)
metadata.negativePromptstringNoNegative prompt; recommended "blurry, watermark, distorted, low quality"
Do not pass the generateAudio field! Veo 3.1 is natively audio-enabled; passing this parameter returns INVALID_ARGUMENT. To control audio, write the intent into your prompt: "waves, distant seabirds, low wind sounds".
Parameter precedence:
  • Duration: metadata.durationSeconds > seconds > 8 (send seconds; duration is not recognized)
  • Resolution: metadata.resolution > size > 720p
  • Aspect: explicit metadata.aspectRatio > inferred from size > 16:9

Response Format

Step 1 - immediately after submission

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

Step 2 - polling response (in progress)

{
  "id": "task_xxxxxxxxxxxxxxxx",
  "task_id": "task_xxxxxxxxxxxxxxxx",
  "object": "video",
  "model": "veo-3.1-fast-generate-preview",
  "status": "in_progress",
  "progress": 50,
  "created_at": 1775025000
}

Step 2 - polling response (completed)

{
  "id": "task_xxxxxxxxxxxxxxxx",
  "task_id": "task_xxxxxxxxxxxxxxxx",
  "object": "video",
  "model": "veo-3.1-fast-generate-preview",
  "status": "completed",
  "progress": 100,
  "created_at": 1775025000,
  "completed_at": 1775025090
}
⚠️ Response field gotchas
  • id and task_id are both returned with the same value; downstream should standardize on task_id (compatible with the existing Reverse channel)
  • No CDN / public URL is returned — no video_url / data.url in the response; the video can only be retrieved as an MP4 binary stream via GET /v1/videos/{task_id}/content (requires auth header). Frontends cannot hit this endpoint directly — download server-side and re-host on your own OSS / CDN
  • progress is coarse-grained — jumps only between 0 / 50 / 100, do not use for percentage bars
  • status: "failed" may not include a detailed error field; usually content review or parameter errors. Just retry or adjust the prompt
  • /content returns 400 occasionally right after status flips to completed; retry after 4 seconds (all code samples above have this baked in)
This endpoint is an async task entry. Billing happens when the task reaches completed, charged per request by model name (fast $0.3 / standard $1.2, see Pricing). POST submission, polling, and download themselves are not billed; failed tasks are also not billed.

Authorizations

Authorization
string
header
required

API Key from APIYI console (Default group + Pay-per-request or Pay-as-you-go Priority Token; pure Pay-as-you-go not supported)

Body

application/json
model
enum<string>
default:veo-3.1-fast-generate-preview
required

Model ID (per-request billing, duration / resolution do not affect price):

  • veo-3.1-fast-generate-preview — $0.3/request, top pick for iteration / batch generation
  • veo-3.1-generate-preview — $1.2/request, for final delivery / 4K scenarios
Available options:
veo-3.1-fast-generate-preview,
veo-3.1-generate-preview
prompt
string
required

Video generation prompt; describe in detail: scene + subject + action + camera + lighting + style.

Audio intent also goes in the prompt (e.g. "waves, distant seabirds, low wind sounds"). Do not pass generateAudio — upstream rejects with INVALID_ARGUMENT.

Example:

"A coastal lighthouse at dusk, slow push-in, waves lapping the rocks, distant seabirds, cinematic lighting, steady camera"

seconds
enum<string>
default:8

Video length. The field name is seconds (not duration), a string enum (not number):

  • "4" — 4 sec, 720p only
  • "6" — 6 sec, 720p only
  • "8" — 8 sec (default), required at 1080p / 4k

Sending duration instead is silently ignored → length falls back to the default 4 sec (720p returns no error but only outputs 4 sec; 1080p/4k errors with ... but got 4). Passing a number (8) returns parse_request_failed: cannot unmarshal number into Go struct field ... duration of type string.

Available options:
4,
6,
8
size
enum<string>
default:1280x720

Output pixel dimensions; lower precedence than metadata.resolution:

  • 1280x720 / 720x1280 — 720p (default)
  • 1920x1080 / 1080x1920 — 1080p (seconds must be "8")
  • 3840x2160 / 2160x3840 — 4k (seconds must be "8", 4–6× slower render)
Available options:
1280x720,
720x1280,
1920x1080,
1080x1920,
3840x2160,
2160x3840
metadata
object

Wrapper for fine-grained generation parameters. Higher precedence than the top-level size etc.:

  • Duration resolution order: metadata.durationSeconds > seconds > 8 (send seconds; duration is not recognized)
  • Resolution resolution order: metadata.resolution > size > 720p

Response

Task submitted; returns task_id and queued status

id
string

Task ID (matches task_id; downstream should standardize on task_id)

Example:

"task_xxxxxxxxxxxxxxxx"

task_id
string

Task ID for subsequent polling and download

Example:

"task_xxxxxxxxxxxxxxxx"

object
string

Object type, fixed to video

Example:

"video"

model
string

Model ID used for this task

Example:

"veo-3.1-fast-generate-preview"

status
enum<string>

Task status:

  • queued — submitted, awaiting processing
  • in_progress — generating
  • completed — done, downloadable (/v1/videos/{task_id}/content)
  • failed — failed (not billed), retry possible
Available options:
queued,
in_progress,
completed,
failed
Example:

"queued"

progress
integer

Generation progress (coarse-grained, jumps only between 0 / 50 / 100, do not use for percentage bars)

Example:

0

created_at
integer

Task creation Unix timestamp (seconds)

Example:

1775025000

completed_at
integer

Task completion Unix timestamp (seconds); only present for completed status

Example:

1775025090