Image-to-video
Animate a still image into an MP4 by passing it as the first frame of a video-generation task. This page walks through a runnable Python script plus the parameters, routes, and error cases that matter for image-to-video.
Before you start
Section titled “Before you start”pip install requestsMIRAPI_API_KEYexported as an environment variable- A video-capable model enabled for your key
- A first-frame image reachable as an HTTP(S) URL or base64 data URI
How image-to-video works
Section titled “How image-to-video works”Video generation is asynchronous: the API returns a task right away, and you poll until the clip is ready.
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.
The image field is what makes a request image-to-video: the model uses it as the first frame and keeps the composition, so you describe the motion in prompt instead of re-describing the scene.
The script
Section titled “The script”import base64import osimport timeimport requests
BASE = "https://api.mirapi.ai/v1"HEADERS = {"Authorization": f"Bearer {os.environ['MIRAPI_API_KEY']}"}POLL_INTERVAL = 10DEADLINE_SECONDS = 600
def image_to_data_uri(path: str) -> str: mime = "image/png" if path.endswith(".png") else "image/jpeg" with open(path, "rb") as f: return f"data:{mime};base64,{base64.b64encode(f.read()).decode()}"
# Pass an HTTP(S) URL, or convert a local file to a data URI:first_frame = "https://example.com/first-frame.png"# first_frame = image_to_data_uri("first-frame.png")
resp = requests.post( f"{BASE}/video/generations", headers=HEADERS, json={ "model": "deepseek-chat", # replace with a video model id "prompt": "The photo comes to life; the person waves at the camera", "image": first_frame, "duration": 5, }, timeout=60,)resp.raise_for_status()task_id = resp.json()["task_id"]
deadline = time.time() + DEADLINE_SECONDSwhile time.time() < deadline: task = requests.get( f"{BASE}/video/generations/{task_id}", headers=HEADERS, timeout=30 ).json() if task["status"] == "completed": break if task["status"] == "failed": raise SystemExit(f"Video failed: {task.get('error')}") time.sleep(POLL_INTERVAL)else: raise TimeoutError(f"Task {task_id} did not finish within {DEADLINE_SECONDS}s")
video = requests.get(task["url"], timeout=60)video.raise_for_status()with open("animated.mp4", "wb") as f: f.write(video.content)print("Saved animated.mp4")The script submits once, then polls every 10 seconds with a 10-minute deadline. It exits on a failed status, raises on timeout, and otherwise downloads the completed task’s url.
First-frame tips
Section titled “First-frame tips”- Hosted images take an HTTP(S) URL; local files become a base64 data URI — both are accepted.
- Describe the motion in
prompt, not the scene. The model already has the first frame’s composition. durationis billed output; start with 5 seconds and extend only if the model supports longer clips.- Make sure the image URL is directly downloadable — a plain image response, not an HTML page, redirect wall, or login page.
Request parameters
Section titled “Request parameters”The unified and Kling routes accept the same JSON body:
| Parameter | Type | Required | Description |
|---|---|---|---|
model |
string | yes | Video-capable model id from the catalogue. |
prompt |
string | yes | Text description of the motion to animate. |
image |
string | yes | First-frame image: 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. |
image is the field that turns the request into image-to-video; omit it and the same route behaves as text-to-video.
Task status
Section titled “Task status”status is one of queued, in_progress, completed, failed. A completed task carries 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.
{ "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.
Endpoint routes
Section titled “Endpoint routes”Video generation is OpenAI-compatible only — there is no Anthropic or Gemini protocol variant. MirAPI exposes four route families that all follow submit → poll → download, but differ in request format and response shape.
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.
OpenAI / Sora format
Section titled “OpenAI / Sora format”POST /v1/videos—multipart/form-datawithmodel,prompt,seconds(a string), and aninput_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.
Kling format
Section titled “Kling format”POST /kling/v1/videos/image2video— the same JSON body as the unified route; takesimagefor the first frame.GET /kling/v1/videos/image2video/{task_id}— poll status. Thetext2videoroute (/kling/v1/videos/text2video) shares this shape withoutimage.
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.
Errors and troubleshooting
Section titled “Errors and troubleshooting”| Status | Meaning | What to do |
|---|---|---|
400 |
Invalid parameters | Check the model id, and that duration/width/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 | Retry idempotent requests safely. |
Every error message ends with a request ID — include it in support tickets.
- Task stays
queuedorin_progress— normal; jobs can take minutes. Keep polling at a steady interval. - Generation
failed— read theerrorfield, confirm the model has video output, and check that the reference image is reachable and in a supported format. 404on the content endpoint — the task is notcompletedyet; poll the status endpoint first.
Approximate cost
Section titled “Approximate cost”Video is billed per second of generated output, settled when the job completes. Failed jobs produce no output and are not charged.
- Balance exhausted → the request fails with
403; top up in the console and the same key recovers immediately. - Reconcile spend in the console billing log or with
GET /api/log/token(per-request detail).
Related links
Section titled “Related links”- Video generation guide — full reference for models, parameters, and routes
- Text-to-video tutorial — submit/poll/download script with timeout and failure branches
- Models & pricing — find video-capable models and per-second pricing
- Billing & top-ups — how balance, top-ups, and reconciliation work