Generate a video
October 12, 2026
Table of contents
Ask Muse for one video of about 10 seconds with sound. Describe it in prompt, and optionally pass input files: up to 9 images as image_1..image_9, 3 videos as video_1..video_3 and 3 audio files as audio_1..audio_3.
- Text to video: no inputs.
- Image to video: animate
image_1, an uploaded image or one Muse made. Write a line in quotes and the person in the photo speaks it, a talking photo. - Video to video: edit
video_1, for example replace a person in it with someone fromimage_1.
The finished job lists the video in result.assets[] as an assetId. Download it with GET /assets/assetId, or pass it as video_N to the next video job. result.text is the agent’s last message.
Prompt, aspect ratio and length
Muse is an agent, not a fixed generator. There are no model, aspect ratio, resolution or duration parameters: describe the video in the prompt.
- Aspect ratio: ask for it explicitly, best as
in exactly <ratio> aspect ratio. Image to video keeps the start image’s shape unless the prompt asks for another ratio. - Length: every video is about 10 seconds.
- Sound: every video has audio. Dialogue written in quotes in the prompt is spoken.
- Resolution: about 720p, MP4 (H.264, 24 fps). Asking for 1080p or 4K did not add detail when we tested it (October 2026), though Muse may change this.
| Asked for | Video |
|---|---|
16:9 | 1280×720 or 1248×704 |
9:16 | 720×1280 or 704×1248 |
3:4 | 832×1104 |
Inputs
| Parameter | Takes | Count |
|---|---|---|
image_1 .. image_9 | an image asset id | up to 9 |
video_1 .. video_3 | a video asset id | up to 3 |
audio_1 .. audio_3 | an audio asset id | up to 3 |
- An asset id comes from POST /assets (your own file) or from
result.assets[]of an earlier job (a file Muse made). A generated file needs no download and no upload to be used again. - Each parameter takes an asset of its own type. An image asset in a
video_Nparameter answers400. - All inputs of one job must live on the same Muse account, and the job runs on that account. If you also send
email, it must be that account. - Name the inputs in the prompt, for example
the person from image_2 wearing the jacket from image_1. The API tells Muse which file each name stands for. A prompt that names an input you did not send answers400.
Our tests used image and video inputs. Muse decides how to use an audio file from what the prompt says about it.
Sync, async and webhooks
- Every generation runs as a job on our side, which follows it for up to 10 minutes. In our tests an image took 20 to 60 seconds and a video 60 to 140 seconds. A job that is still running 15 minutes after it started is reported as failed with
504. - Sync (the default): the POST waits for the job and answers
200with the finished job record, or with the job’s error code and the failed job record. If the job is still running after about 100 seconds, the POST answers202with the job as it stands (status: "started"): fetch the result with GET /jobs/jobid. Always checkstatus: onlycompletedhas aresult. - Async: pass
async: true. The POST answers201at once with the job instatus: "started". Poll GET /jobs/jobidor wait for the webhook. replyUrlreceives onePOSTof the final job record, as JSON, when the job completes or fails, for sync and async requests alike. It does not make a request async. WithoutreplyUrl, the account-levelreplyUrlset with POST /account is used.- Each Muse account runs at most
maxJobsjobs at a time (default2, range1to5, set with POST /accounts). GET /jobs shows what is running. Job records are kept for 7 days.
A video took 60 to 140 seconds in our tests, so a sync request answers 202 for the slower ones. Swapping a person in a video took the longest. Send videos with async: true when you would rather poll or use the webhook. A video uses 2 to 3 percent of the weekly allowance on the free plan. See Weekly allowance.
https://api.useapi.net/v1/muse/jobs/video
Request Headers
Authorization: Bearer {API token}
Content-Type: application/json
# Alternatively you can use multipart/form-data
# Content-Type: multipart/form-data
API tokenis required, see Setup useapi.net for details.
Request Body
{
"prompt": "Edit video_1: replace the rider with the woman from image_1 (her face, pink hair in two buns, colorful makeup and tattoos), and keep everything else the same: the motorcycle, the beach, the waves and spray, the Golden Gate Bridge, the light, the camera move and the timing. Instead of the original line she shouts: \"Catch me if you can, San Francisco!\" Keep the 16:9 frame and the same length.",
"video_1": "user:12345-email:6578616d706c6540676d61696c2e636f6d-video:media-generation-shoreline-motorcycle-ride-16x9-0-acd2974b-63e0-4539-8279-3a9384f4ccb0.mp4",
"image_1": "user:12345-email:6578616d706c6540676d61696c2e636f6d-image:fb07d74a0e9144e4ae5bb3f6f2d1045abbf209e20693b9cb4833d932e2b3ffd7.jpg",
"async": true,
"replyUrl": "https://your-domain.com/webhook",
"replyRef": "rider-swap-1"
}
promptis required, what to make. Name the inputs asimage_1,video_1,audio_1… and say the aspect ratio and the spoken lines here.
Maximum length:5000characters.image_1..image_9are optional, input images as asset ids from POST /assets or from an earlier job’sresult.assets[]. See Inputs.video_1..video_3are optional, input videos as asset ids.audio_1..audio_3are optional, input audio files as asset ids.emailis optional, the account to run on. When omitted, the account that holds the inputs is used, or without inputs a healthy account with allowance left and a freemaxJobsslot.asyncis optional,trueto answer201at once and run the job in the background. Default:false.replyUrlis optional, a publichttpsURL that receives onePOSTof the job record when the job completes or fails. It works with sync and async requests. Auseapi.netURL is refused.
Callback body has the same JSON shape as GET /jobs/jobidresponse.
Maximum length:1024characters.replyRefis optional, your own reference echoed back in the job record.
Maximum length:1024characters.
Responses
-
200 OK — sync mode, the video is ready. This one animated an uploaded image, 83 seconds after the request.
{ "jobid": "j1012012156286615380v-u12345-email:[email protected]:20842037-a004-4dba-b640-225a8197385b-bot:muse", "type": "video", "email": "[email protected]", "status": "completed", "created": "2026-10-12T01:21:56.286Z", "request": { "prompt": "Animate image_1: she turns toward the camera and smiles, the wind moves her dress. Keep the vertical 9:16 frame.", "image_1": "user:12345-email:6578616d706c6540676d61696c2e636f6d-image:558c51be411f66026e9ca195d9330e5582d5e5807e455926ad29d6cfbe02e302.webp" }, "chatId": "20842037-a004-4dba-b640-225a8197385b", "messageId": "28d4035a-7eec-4720-95a9-84a8dd1e9f2b", "updated": "2026-10-12T01:23:18.960Z", "completed": "2026-10-12T01:23:18.960Z", "result": { "assets": [ { "assetId": "user:12345-email:6578616d706c6540676d61696c2e636f6d-video:media-generation-create-one-video-animate-image-0-def1ac18-8024-4a8b-9bce-a2700d683f45.mp4", "type": "video", "mime": "video/mp4", "bytes": 5066535 } ], "text": "Done — here's your clip, vertical 9:16, 10 seconds." } } -
201 Created —
async: true: the job is running. Fetch the result with GET /jobs/jobid, or wait for the webhook.{ "jobid": "j1012014112831908109v-u12345-email:[email protected]:3e8bc834-f243-4172-8f6e-9fc28e1a107c-bot:muse", "type": "video", "email": "[email protected]", "status": "started", "created": "2026-10-12T01:41:12.831Z", "request": { "prompt": "Animate image_1 into a video with the same portrait frame: the young woman slowly lifts her eyes to the camera, her smile widens, she gently touches the rose on her shoulder and says softly: \"It has been a hundred years... and you still remember me.\" Keep her face exactly as in image_1.", "image_1": "user:12345-email:6578616d706c6540676d61696c2e636f6d-image:media-generation-restored-portrait-0-69c7e16d-eb86-4932-b940-8d688f2b95dc.webp", "async": true }, "chatId": "3e8bc834-f243-4172-8f6e-9fc28e1a107c" } -
202 Accepted — sync mode, the job was still running after about 100 seconds. The body is the job record with
status: "started", as in201. The job keeps running: fetch it with GET /jobs/jobid. -
400 Bad Request — a parameter is missing or invalid, an input is not of its parameter’s type, the inputs live on different accounts, or the prompt names an input that was not sent.
{ "error": "The prompt names image_2, but 'image_2' was not provided in the request body", "code": 400 }{ "error": "Parameter image_1 must be a image asset, this one is a video", "code": 400 }{ "error": "Please configure at least one account at https://useapi.net/docs/api-muse-v1/post-muse-accounts", "code": 400 } -
Invalid API token.
{ "error": "useapi.net ⁝ Unauthorized", "code": 401 } -
403 Forbidden — an input asset id belongs to another API token:
{ "error": "This asset id does not belong to this API token", "code": 403 }Or, in sync mode, Muse refused to make the video: the body is the failed job record, and
error.messagehas the agent’s own words. Change the prompt or the inputs. See error codes. -
404 Not Found — the account named by
email, or the account an input lives on, is not added.{ "error": "Account [email protected] is not configured", "code": 404 } -
412 Precondition Failed — the account’s weekly Muse allowance is used up (or every account’s, without
email). It counts again after the reset time in the message. In sync mode a job that Muse turned down for this reason answers412with the failed job record.{ "error": "All 2 healthy accounts have used up their Muse allowance. GET /accounts/{email} re-reads it", "code": 412 } -
429 Too Many Requests — the account is running
maxJobsjobs, or every account is. The response carriesRetry-After: 30. In sync mode429can also be a job that Muse turned down because it was limiting the account: retry in about a minute.{ "error": "All 1 accounts are running their maximum number of jobs (maxJobs), please retry shortly", "code": 429 } -
502 Bad Gateway — sync mode, Muse made the video but the API cannot return it, for example because of an unusual file name. The body is the failed job record and
error.messagesays why. The video is in the job’s chat, see GET /jobs/jobid?history=true. Running the job again uses the allowance again. -
503 Service Unavailable — nothing was made: the job could not be started, Muse could not be reached, or Muse failed to make the video this time and said so. The body is the failed job record. Run it again.
-
504 Gateway Timeout — sync mode, Muse did not finish in time or the connection dropped after the job was sent. The body is the failed job record. Muse may still have made the video: check GET /jobs/
jobid?history=true before running it again. -
596 Account Error
The account needs your attention, and it is paused until you add it again. You receive an email when this happens. There are two causes.
Meta asks for a security check on the account. Sign in once at muse.ai in a browser, complete the check, then add the account again with POST /accounts using the same email and password. The API keeps using its session, so no new sign-in is needed.
{ "error": "Account [email protected]: Meta asks for a security check on account [email protected] (\"This account must complete a security checkpoint on the web\"). Sign in once at https://muse.ai in a browser, complete the check, then add the account again with POST /accounts", "code": 596 }Or Muse refused to sign the account in again when its session needed renewing, most often because the password was changed. Add the account again with the current password.
{ "error": "Account [email protected]: Muse refused to sign in account [email protected] (…). Update its password with POST /accounts, or sign in at https://muse.ai to clear the block, then re-add it", "code": 596 }Without
emailand without inputs, the same code means no added account is healthy:{ "error": "No healthy Muse account configured. Check GET /accounts", "code": 596 }
Model
The job record, type: "video". GET /jobs/jobid and the replyUrl webhook return the same shape. Errors before a job is created return { error, code }.
{ // TypeScript, all fields are optional
jobid: string // j<nonce><i|v>-u<id>-email:<email>-task:<chat id>-bot:muse
type: 'image' | 'video'
email: string // the Muse account the job runs on
status: 'started' | 'completed' | 'failed'
created: string // ISO 8601, when the job was accepted
updated?: string // ISO 8601, the last change
completed?: string // ISO 8601, when the job completed or failed
request: Record<string, unknown> // your request body as sent
chatId: string // the Muse chat this job created (also inside the jobid)
messageId?: string // Muse's id of the job's message in that chat, once it was sent
replyUrl?: string
replyRef?: string
error?: { // status 'failed'
code: number // see the error codes on GET /jobs/{jobid}
message: string // when Muse refused, its own words
}
result?: { // status 'completed'
assets: { // the generated files, usually one
assetId: string // download it with GET /assets/{assetId}, or pass it as image_N / video_N of the next job
type: 'image' | 'video' | 'audio'
mime: string // e.g. image/webp, video/mp4
bytes: number
}[]
text: string | null // the agent's last message, e.g. "Here's your image."
}
history?: unknown[] // GET /jobs/{jobid}?history=true only: the job's chat turn as Muse sent it
historyError?: string // GET /jobs/{jobid}?history=true only: why the chat could not be read
}
Examples
-
curl -X POST "https://api.useapi.net/v1/muse/jobs/video" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer …" \ -d '{ "prompt": "Cinematic 16:9 landscape video, about 10 seconds: an old mechanic in a dusty 1920s garage polishes the chrome headlamp of a vintage black motorcycle, then looks up at the camera and says warmly: \"They don'\''t build them like this anymore.\" Warm tungsten light, slow push-in.", "async": true }' -
const token = "API token"; const apiUrl = "https://api.useapi.net/v1/muse"; const headers = { "Content-Type": "application/json", "Authorization": `Bearer ${token}` }; const image_1 = "assetId of an image, from POST /assets or an earlier job"; const response = await fetch(`${apiUrl}/jobs/video`, { method: "POST", headers, body: JSON.stringify({ image_1, async: true, prompt: "Animate image_1: she turns to the camera and says: \"Best seat in San Francisco.\" Keep her face exactly as in image_1." }) }); let job = await response.json(); while (job.status === "started") { await new Promise(resolve => setTimeout(resolve, 10000)); job = await (await fetch(`${apiUrl}/jobs/${encodeURIComponent(job.jobid)}`, { headers })).json(); } if (job.status === "completed") { const { assetId } = job.result.assets[0]; const video = await fetch(`${apiUrl}/assets/${encodeURIComponent(assetId)}`, { headers }); console.log("video bytes", (await video.arrayBuffer()).byteLength, job.result.text); } else console.log("job", job); -
import requests, time from urllib.parse import quote token = "API token" apiUrl = "https://api.useapi.net/v1/muse" headers = {"Content-Type": "application/json", "Authorization": f"Bearer {token}"} image_1 = "assetId of an image, from POST /assets or an earlier job" data = {"image_1": image_1, "async": True, "prompt": "Animate image_1: she turns to the camera and says: \"Best seat in San Francisco.\" Keep her face exactly as in image_1."} job = requests.post(f"{apiUrl}/jobs/video", headers=headers, json=data).json() while job.get("status") == "started": time.sleep(10) job = requests.get(f"{apiUrl}/jobs/{quote(job['jobid'], safe='')}", headers=headers).json() if job.get("status") == "completed": asset_id = job["result"]["assets"][0]["assetId"] video = requests.get(f"{apiUrl}/assets/{quote(asset_id, safe='')}", headers=headers) with open("video.mp4", "wb") as f: f.write(video.content) else: print(job)