Video API

Video generation

Generate video from a prompt, or animate a starting image, with the video models listed below.

POSThttps://api.oxyy.ai/v1/videos/generations
Video is always a job. This endpoint answers 202 Accepted with a job id — never a video. Poll the job until its status is completed, then read the file from result.data[0].url. A clip typically takes 30–120 seconds.

Parameters

ParameterTypeRequiredDescription
modelstringRequiredA video model id, e.g. grok-imagine-video.
promptstringRequired*What should happen in the shot. *Optional when you supply an image to animate.
imagefile|stringOptionalA starting frame — an upload, a URL or a data URL. Turns the request into image-to-video.
durationnumberOptionalClip length in seconds, 1–60. Providers clamp to the lengths each model supports, and you are billed for the length actually produced. Default 4
secondsstring|integerOptionalOpenAI's spelling of the same thing. One of:4812
resolutionstringOptionalOne of:480p720p1080p4k
sizestringOptionalWIDTHxHEIGHT, e.g. 1280x720. An alternative to resolution.
aspect_ratiostringOptionale.g. 16:9, 9:16.
negative_promptstringOptionalWhat to keep out of the shot.
generate_audiobooleanOptionalAsk for a soundtrack on the models that produce one.
person_generationstringOptionalProvider-side policy for depicting people.
seedintegerOptionalReproducibility, where supported.
nintegerOptionalClips to generate, 1–4. Not every provider returns more than one. Default 1
syncbooleanOptionalWait for the clip on the request instead of returning a job. Only for short clips — long generations will hit the request timeout. Default false
userstringOptionalA stable id for your own end user.

Submitting a job

import os, requests

headers = {
    "Authorization": f"Bearer {os.environ['OXYY_API_KEY']}",
    "Content-Type": "application/json",
}

# Video is asynchronous: this returns 202 with a job, not a video.
response = requests.post(
    "https://api.oxyy.ai/v1/videos/generations",
    headers=headers,
    json={
        "model": "grok-imagine-video",
        "prompt": "A serene lake with mountains, slow dolly in",
        "duration": 8,
        "resolution": "1080p",
        "aspect_ratio": "16:9",
    },
)
job = response.json()
print(response.status_code, job["id"], job["status"])
curl https://api.oxyy.ai/v1/videos/generations \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OXYY_API_KEY" \
  -d '{
    "model": "grok-imagine-video",
    "prompt": "A serene lake with mountains",
    "duration": 8,
    "resolution": "1080p"
  }'
202 Accepted
HTTP/1.1 202 Accepted

{
  "id": "gen_01JQXY8M3K2T4V6W8Z",
  "object": "generation.job",
  "status": "queued",
  "type": "video",
  "model": "grok-imagine-video",
  "created_at": 1700000000,
  "expires_at": 1700086400,
  "_links": {
    "self": "/v1/videos/generations/gen_01JQXY8M3K2T4V6W8Z",
    "cancel": "/v1/videos/generations/gen_01JQXY8M3K2T4V6W8Z"
  }
}

Polling for the result

GEThttps://api.oxyy.ai/v1/videos/generations/{id}

Poll every five seconds or so. status moves through queued → processing → completed, with progress as a percentage; the terminal failures are failed and cancelled.

import os, time, requests

headers = { "Authorization": f"Bearer {os.environ['OXYY_API_KEY']}" }
job_id = "gen_..."  # the `id` the 202 returned

while True:
    job = requests.get(
        f"https://api.oxyy.ai/v1/videos/generations/{job_id}",
        headers=headers,
    ).json()

    if job["status"] == "completed":
        # The video lives under result.data[0].url
        print("Video:", job["result"]["data"][0]["url"])
        print("Charged:", job["usage"]["credits_consumed"])
        break
    if job["status"] in ("failed", "cancelled"):
        print("Failed:", job.get("error"))
        break

    print(f"{job['status']} — {job['progress']}%")
    time.sleep(5)
const headers = { Authorization: `Bearer ${process.env.OXYY_API_KEY}` };
const jobId = 'gen_...';

while (true) {
  const job = await fetch(
    `https://api.oxyy.ai/v1/videos/generations/${jobId}`,
    { headers }
  ).then((r) => r.json());

  if (job.status === 'completed') {
    console.log('Video:', job.result.data[0].url);
    break;
  }
  if (job.status === 'failed' || job.status === 'cancelled') {
    console.error('Failed:', job.error);
    break;
  }
  console.log(`${job.status} — ${job.progress}%`);
  await new Promise((r) => setTimeout(r, 5000));
}
# 1. Submit — the 202 body carries the job id.
curl -X POST https://api.oxyy.ai/v1/videos/generations \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OXYY_API_KEY" \
  -d '{"model": "grok-imagine-video", "prompt": "A serene lake", "duration": 8}'

# 2. Poll until status is completed, failed or cancelled.
curl https://api.oxyy.ai/v1/videos/generations/gen_abc123 \
  -H "Authorization: Bearer $OXYY_API_KEY"

# 3. Cancel a job you no longer want.
curl -X DELETE https://api.oxyy.ai/v1/videos/generations/gen_abc123 \
  -H "Authorization: Bearer $OXYY_API_KEY"

Completed job

Response
{
  "id": "gen_01JQXY8M3K2T4V6W8Z",
  "object": "generation.job",
  "status": "completed",
  "type": "video",
  "model": "grok-imagine-video",
  "progress": 100,
  "created_at": 1700000000,
  "started_at": 1700000004,
  "completed_at": 1700000098,
  "expires_at": 1700086400,
  "result": {
    "created": 1700000098,
    "data": [
      {
        "url": "https://api.oxyy.ai/storage/videos/2026/01/abc123.mp4",
        "duration": 8,
        "resolution": "1080p"
      }
    ]
  },
  "usage": { "credits_consumed": 3.2 }
}

Failed job

Response
{
  "id": "gen_01JQXY8M3K2T4V6W8Z",
  "object": "generation.job",
  "status": "failed",
  "type": "video",
  "model": "grok-imagine-video",
  "progress": 30,
  "created_at": 1700000000,
  "error": {
    "message": "The video was filtered by the provider's safety system",
    "code": "CONTENT_FILTER"
  }
}

Managing jobs

GEThttps://api.oxyy.ai/v1/videos/generationsyour jobs; ?status=&limit=&offset=
DELETEhttps://api.oxyy.ai/v1/videos/generations/{id}cancel
POSThttps://api.oxyy.ai/v1/videos/image-to-videoanimate an image

A job is yours alone — another account's id answers 404, not 403. Jobs expire at expires_at; download the file before then or re-host it yourself.

Available models