Video generation
Video generation is asynchronous. A clip takes longer than a chat reply, so the API does not block: you submit a task, poll it, and download the result.
Choose a model
Section titled “Choose a model”Video models are listed in GET /v1/models alongside chat models. Filter for entries whose output_modalities includes video, and check their pricing: video is billed per second of output, not per token. The vendor/ prefix in the Platform is for browsing only; it is not part of the model id you send to the API.
The three steps
Section titled “The three steps”POST /v1/video/generations: submit the job and receive atask_id.GET /v1/video/generations/{task_id}: poll untilstatusiscompletedorfailed.- Download the MP4 from the
urlin the completed task.
import osimport timeimport requests
BASE = "https://api.mirapi.ai/v1"headers = {"Authorization": f"Bearer {os.environ['MIRAPI_API_KEY']}"}
# Step 1: submitresp = requests.post( f"{BASE}/video/generations", headers=headers, json={ "model": "deepseek-chat", "prompt": "A golden retriever playing fetch on a sunny beach", "duration": 5, "width": 1280, "height": 720, "fps": 30, },)resp.raise_for_status()task_id = resp.json()["task_id"]
# Step 2: pollwhile True: task = requests.get(f"{BASE}/video/generations/{task_id}", headers=headers).json() if task["status"] in ("completed", "failed"): break time.sleep(10)
if task["status"] == "failed": raise SystemExit(f"Video failed: {task.get('error')}")
# Step 3: downloadvideo = requests.get(task["url"])open("output.mp4", "wb").write(video.content)# Step 1curl -X POST https://api.mirapi.ai/v1/video/generations \ -H "Authorization: Bearer $MIRAPI_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model": "deepseek-chat", "prompt": "A city skyline at dusk", "duration": 5}'
# Step 2 — repeat until "completed"curl https://api.mirapi.ai/v1/video/generations/abcd1234efgh \ -H "Authorization: Bearer $MIRAPI_API_KEY"
# Step 3 — download the URL from the completed taskcurl -o output.mp4 "https://…"The completed task’s url is a temporary link, so download it right away.
Request parameters
Section titled “Request parameters”The unified route takes a JSON body with these fields:
| Parameter | Type | Required | Description |
|---|---|---|---|
model |
string | yes | Video-capable model id from the catalog. |
prompt |
string | yes | Text description of the clip. |
image |
string | no | First-frame image for image-to-video: HTTP(S) URL or base64 data URI. |
duration |
number | no | Target length in seconds. |
width |
integer | no | Output width in pixels. |
height |
integer | no | Output height in pixels. |
fps |
integer | no | Frame rate. |
seed |
integer | no | Seed for reproducible output (not guaranteed by every model). |
n |
integer | no | Number of videos to generate. |
response_format |
string | no | Response format (e.g. url). |
user |
string | no | End-user identifier for abuse tracking. |
metadata |
object | no | Model-specific extras (negative_prompt, style, quality_level, …) where the model accepts them. |
Task status
Section titled “Task status”status is one of queued, in_progress, completed, failed. A completed task includes the video url, format (e.g. mp4), and a metadata object with the actual duration, width, height, and fps; a failed task includes an error with a code and message.
{ "task_id": "abcd1234efgh", "status": "completed", "url": "https://…", "format": "mp4", "metadata": { "duration": 5, "width": 1280, "height": 720 }}Poll every 10–30 seconds. A job can take minutes, depending on the model and resolution.
Image-to-video
Section titled “Image-to-video”Pass an optional image field (URL or base64 data URI) to control the first frame. The model keeps the composition, so describe the motion in prompt rather than re-describing the scene:
resp = requests.post( f"{BASE}/video/generations", headers=headers, json={ "model": "deepseek-chat", "prompt": "The photo comes to life and the person waves", "image": "https://example.com/first-frame.png", "duration": 5, },)Endpoint routes
Section titled “Endpoint routes”MirAPI exposes four route families for video. They all follow the same submit, poll, and download flow, but differ in request format and response shape.
OpenAI / Sora format
Section titled “OpenAI / Sora format”POST /v1/videos:multipart/form-datawithmodel,prompt,seconds(a string), and an optionalinput_referenceimage file. Returns anid(nottask_id).GET /v1/videos/{task_id}: task status, includingprogressandcreated_at.GET /v1/videos/{task_id}/content: the finished file, returned asvideo/mp4.
Unified format
Section titled “Unified format”POST /v1/video/generations: JSON body (the request parameters above). Returnstask_idandstatus.GET /v1/video/generations/{task_id}: status; a completed task carriesurl,format, andmetadata.
Kling format
Section titled “Kling format”POST /kling/v1/videos/text2videoandPOST /kling/v1/videos/image2video: the same JSON body as the unified route; the image variant takesimage.- Poll via
GET /kling/v1/videos/text2video/{task_id}andGET /kling/v1/videos/image2video/{task_id}.
Jimeng format
Section titled “Jimeng format”POST /jimeng/: the native Jimeng API.ActionandVersionare query parameters:ActionisCVSync2AsyncSubmitTask(submit) orCVSync2AsyncGetResult(fetch result), andVersionis the API version (e.g.2022-08-31). The JSON body carriesreq_key,prompt, and an optionalbinary_data_base64image array.
Billing
Section titled “Billing”Video is billed per second of generated output, and the charge is settled when the job completes. Failed jobs produce no output and are not charged.
- If your balance is exhausted, the request fails with
403; top up in the Platform and the same key works again immediately. - Reconcile spend in the Platform billing log,
GET /api/usage/token(account-level summary), orGET /api/log/token(per-request detail).
Errors and troubleshooting
Section titled “Errors and troubleshooting”| Status | Meaning | What to do |
|---|---|---|
400 |
Invalid parameters | Check the model id and make sure duration, width, and height are numeric. |
401 |
Missing or invalid key | Verify Authorization: Bearer. |
403 |
Key valid but rejected | Top up (empty balance), or check the model whitelist, quota, and IP allowlist. |
404 |
Task or content not found | The task does not exist, or the video is not completed yet. |
429 |
Rate limited | Back off exponentially (1s → 2s → 4s, cap ~30s); there is no Retry-After header. |
500 |
Gateway or upstream failure | Idempotent requests are safe to retry. |
Every error message ends with a request ID. Include it in support tickets.
- Task stays
queuedorin_progressfor a while: this is normal, because jobs can take minutes. Keep polling at a steady interval. - Generation
failed: read theerrorfield, confirm the model has video output, and check that any reference image is reachable and in a supported format. 404on the content endpoint: the task is notcompletedyet; poll the status endpoint first.
Related links
Section titled “Related links”- Text-to-video tutorial: full submit/poll/download script with timeout and failure branches
- Image-to-video tutorial: first-frame control and compatibility routes
- Models & pricing: find video-capable models and per-second pricing
- Billing & top-ups: how balance, top-ups, and reconciliation work