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
| Parameter | Type | Required | Description |
|---|---|---|---|
| model | string | Required | A video model id, e.g. grok-imagine-video. |
| prompt | string | Required* | What should happen in the shot. *Optional when you supply an image to animate. |
| image | file|string | Optional | A starting frame — an upload, a URL or a data URL. Turns the request into image-to-video. |
| duration | number | Optional | Clip length in seconds, 1–60. Providers clamp to the lengths each model supports, and you are billed for the length actually produced. Default 4 |
| seconds | string|integer | Optional | OpenAI's spelling of the same thing. One of:4812 |
| resolution | string | Optional | One of:480p720p1080p4k |
| size | string | Optional | WIDTHxHEIGHT, e.g. 1280x720. An alternative to resolution. |
| aspect_ratio | string | Optional | e.g. 16:9, 9:16. |
| negative_prompt | string | Optional | What to keep out of the shot. |
| generate_audio | boolean | Optional | Ask for a soundtrack on the models that produce one. |
| person_generation | string | Optional | Provider-side policy for depicting people. |
| seed | integer | Optional | Reproducibility, where supported. |
| n | integer | Optional | Clips to generate, 1–4. Not every provider returns more than one. Default 1 |
| sync | boolean | Optional | Wait for the clip on the request instead of returning a job. Only for short clips — long generations will hit the request timeout. Default false |
| user | string | Optional | A 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.
