Skip to main content
Unlike chat, image, and audio endpoints, video generation is asynchronous. POST /v1/videos/generations returns a job id immediately and queues the render; you poll GET /v1/videos/generations/{id} until status is completed, at which point the MP4 is in data[0].b64_json. A 2-second clip typically takes 50–90 seconds end-to-end. Jobs are retained for 24 hours after completion. Find video-capable models with supports containing video_generation — see model discovery.

Submit a job

curl https://tokens.flex.ai/v1/videos/generations \
  -H "Authorization: Bearer $FLEXAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Wan2.2-T2V-A14B-Diffusers",
    "prompt": "a paper boat sailing across a puddle, slow motion",
    "duration_seconds": 2
  }'
import os, requests

resp = requests.post(
    "https://tokens.flex.ai/v1/videos/generations",
    headers={"Authorization": f"Bearer {os.environ['FLEXAI_API_KEY']}"},
    json={
        "model": "Wan2.2-T2V-A14B-Diffusers",
        "prompt": "a paper boat sailing across a puddle, slow motion",
        "duration_seconds": 2,
    },
)
resp.raise_for_status()
job_id = resp.json()["id"]
const resp = await fetch("https://tokens.flex.ai/v1/videos/generations", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.FLEXAI_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "Wan2.2-T2V-A14B-Diffusers",
    prompt: "a paper boat sailing across a puddle, slow motion",
    duration_seconds: 2,
  }),
});
const { id: jobId } = await resp.json();
The response is 202 Accepted with { "id": "...", "status": "queued", "created": 1748902000 }.

Poll for the result

import base64, time

while True:
    poll = requests.get(
        f"https://tokens.flex.ai/v1/videos/generations/{job_id}",
        headers={"Authorization": f"Bearer {os.environ['FLEXAI_API_KEY']}"},
    ).json()
    if poll["status"] == "completed":
        with open("out.mp4", "wb") as f:
            f.write(base64.b64decode(poll["data"][0]["b64_json"]))
        break
    if poll["status"] == "failed":
        raise RuntimeError(poll.get("error"))
    time.sleep(5)
import fs from "node:fs";

while (true) {
  const poll = await fetch(`https://tokens.flex.ai/v1/videos/generations/${jobId}`, {
    headers: { Authorization: `Bearer ${process.env.FLEXAI_API_KEY}` },
  }).then((r) => r.json());

  if (poll.status === "completed") {
    fs.writeFileSync("out.mp4", Buffer.from(poll.data[0].b64_json, "base64"));
    break;
  }
  if (poll.status === "failed") throw new Error(JSON.stringify(poll.error));
  await new Promise((r) => setTimeout(r, 5000));
}
# Poll every 5 seconds; pipe to base64 once status is "completed".
JOB_ID=...
while true; do
  STATUS=$(curl -s -H "Authorization: Bearer $FLEXAI_API_KEY" \
    "https://tokens.flex.ai/v1/videos/generations/$JOB_ID" | jq -r '.status')
  [ "$STATUS" = "completed" ] && break
  [ "$STATUS" = "failed" ] && { echo "job failed"; exit 1; }
  sleep 5
done

curl -s -H "Authorization: Bearer $FLEXAI_API_KEY" \
  "https://tokens.flex.ai/v1/videos/generations/$JOB_ID" \
  | jq -r '.data[0].b64_json' | base64 -d > out.mp4

Status values

statusMeaning
queuedAccepted but not yet on a GPU.
processingRender in progress.
completeddata[0].b64_json holds the MP4 bytes.
failederror is populated with details.

Request fields

FieldRequiredNotes
modelYesMust have video_generation in supports.
promptYes1–2000 characters; must not be blank.
duration_secondsNo1–5, default 2. Longer clips proportionally extend render time.

Polling cadence

Poll every 5–10 seconds; faster polling won’t speed up the render and counts against your rate limit. The poll endpoint is cheap (no GPU cost) but still rate-limited per key.

Billing

Video models bill per output second — see pricing[] on GET /api/models for the active rate, or the billing reference.