Genace

How to generate video with an API in Python, including the polling loop

Video generation is asynchronous. The endpoint returns a job, not a video, and the interesting part of the integration is everything after that.

Submit

import os, requests

BASE = "https://genace.ai/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['GENACE_API_KEY']}"}

resp = requests.post(
    f"{BASE}/video/generations",
    headers=HEADERS,
    json={
        "model": "seedance-2.0-fast",
        "prompt": "a cyclist on a coastal road at golden hour",
        "duration_sec": 5,
    },
)
resp.raise_for_status()
job = resp.json()
print(job["job_id"], job["state"], job["credits_held"])

The response is 202 Accepted:

{
  "job_id": "abc123",
  "state": "queued",
  "model": "seedance-2.0-fast",
  "created_at": "2026-09-16T10:00:00Z",
  "credits_held": 25
}

credits_held tells you what this call reserved before anything rendered. Worth logging — it is the number that comes back if the job fails.

Poll

The job endpoint takes a wait parameter that blocks server-side for up to N seconds and returns whatever state it reached. That turns a busy loop into a handful of long requests:

import time

def wait_for(job_id, timeout=600, chunk=30):
    deadline = time.time() + timeout
    while time.time() < deadline:
        r = requests.get(f"{BASE}/jobs/{job_id}", headers=HEADERS,
                         params={"wait": chunk})
        r.raise_for_status()
        job = r.json()
        if job["state"] in ("succeeded", "failed", "canceled"):
            return job
    raise TimeoutError(f"{job_id} did not finish in {timeout}s")

wait=0 returns immediately with the current state, which is what you want for a status endpoint of your own.

The failure case your try/except will miss

This is the part that bites people:

job = wait_for(job_id)

# WRONG — a failed job is a successful HTTP response
url = job["outputs"][0]["url"]

# RIGHT
if job["state"] != "succeeded":
    raise RuntimeError(f"{job['state']}: {job.get('error', {}).get('code')}")
url = job["outputs"][0]["url"]

raise_for_status() sees a 200 and is satisfied. The job failed anyway. Code that only handles exceptions treats this as a finished render and carries on with no output — which usually surfaces much later as a missing file.

Credits for failed jobs are refunded in full, so a retry costs you one render rather than two. Which errors are worth retrying is covered in job state failed and UPSTREAM_FAIL.

Handle the budget branch explicitly

resp = requests.post(f"{BASE}/video/generations", headers=HEADERS, json=payload)

if resp.status_code == 402:
    err = resp.json()["error"]
    print(err["message"])   # "Not enough credits: balance is 12, needs 100"
    return None

if resp.status_code == 429:
    code = resp.json()["error"]["code"]
    # RATE_LIMIT_EXCEEDED clears with time; CONCURRENCY_LIMIT_EXCEEDED
    # clears when one of your own jobs finishes. Retrying blindly only
    # helps in the first case.
    ...

Both 429s look identical by status code and need opposite responses. Branch on error.code, not on the status — RATE_LIMIT_EXCEEDED versus CONCURRENCY_LIMIT_EXCEEDED.

Switching models is a string

json={"model": "veo3", "prompt": "..."}    # no duration_sec — Veo 3 is fixed at 8s

Same endpoint, same auth, same job semantics. Note that Veo 3 rejects duration_sec entirely; passing one returns 400 INVALID_PARAM naming the field.

Do not poll from inside an agent turn

If this code runs as a tool call in an agent loop, the polling loop is the wrong shape — it blocks the turn for minutes and fills the context with nothing. Return the job_id and collect the result elsewhere. The options are in calling a video generation API without blocking your agent.

Models covered on this page

Where these facts come from

  • codebase: src/app/api/v1/video/generations/route.ts — request and 202 response shape
  • keyword check: Bing autocomplete has no validated demand for language-specific generation-API tutorials — this is a developer doc, not a traffic page
  • codebase: src/ai/jobs/http.ts — pollUntilTerminal and the wait parameter
  • codebase: src/ai/api/errors.ts — error response shape