Generate a video

October 12, 2026

Table of contents

  1. Request Headers
  2. Request Body
  3. Responses
  4. Model
  5. Examples
  6. Try It

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 from image_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_N parameter answers 400.
  • 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 answers 400.

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 200 with 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 answers 202 with the job as it stands (status: "started"): fetch the result with GET /jobs/jobid. Always check status: only completed has a result.
  • Async: pass async: true. The POST answers 201 at once with the job in status: "started". Poll GET /jobs/jobid or wait for the webhook.
  • replyUrl receives one POST of 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. Without replyUrl, the account-level replyUrl set with POST /account is used.
  • Each Muse account runs at most maxJobs jobs at a time (default 2, range 1 to 5, 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
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"
}
  • prompt is required, what to make. Name the inputs as image_1, video_1, audio_1… and say the aspect ratio and the spoken lines here.
    Maximum length: 5000 characters.
  • image_1 .. image_9 are optional, input images as asset ids from POST /assets or from an earlier job’s result.assets[]. See Inputs.
  • video_1 .. video_3 are optional, input videos as asset ids.
  • audio_1 .. audio_3 are optional, input audio files as asset ids.
  • email is 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 free maxJobs slot.
  • async is optional, true to answer 201 at once and run the job in the background. Default: false.
  • replyUrl is optional, a public https URL that receives one POST of the job record when the job completes or fails. It works with sync and async requests. A useapi.net URL is refused.
    Callback body has the same JSON shape as GET /jobs/jobid response.
    Maximum length: 1024 characters.
  • replyRef is optional, your own reference echoed back in the job record.
    Maximum length: 1024 characters.
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 in 201. 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
    }
    
  • 401 Unauthorized

    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.message has 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 answers 412 with 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 maxJobs jobs, or every account is. The response carries Retry-After: 30. In sync mode 429 can 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.message says 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 email and 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)
    
Try It
image_4 .. image_9
video_2, video_3, audio_1 .. audio_3