Generate an image

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 image. Describe it in prompt, and optionally pass up to 9 input images as image_1..image_9. With inputs, Muse can combine them into one scene, edit an image, or restore and colorize an old photograph.

The finished job lists the image in result.assets[] as an assetId. Download it with GET /assets/assetId, or pass it as image_N to the next image job or to POST /jobs/video to animate it. result.text is the agent’s last message.

Prompt, aspect ratio and size

Muse is an agent, not a fixed generator. There are no model, aspect ratio or resolution parameters: describe the image in the prompt.

  • Aspect ratio: ask for it explicitly, best as in exactly <ratio> aspect ratio. Muse follows any common ratio.
  • Size: about 2 to 2.5 megapixels, WebP.
Asked for Image
1:1 1600×1600
16:9 / 9:16 2048×1152 / 1152×2048
3:2 / 2:3 1920×1280 / 1280×1920
4:3 / 3:4 1792×1344 / 1344×1792
5:4 / 4:5 1760×1408 / 1408×1760
21:9 2352×1008

Inputs

Parameter Takes Count
image_1 .. image_9 an image asset id up to 9
  • 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.

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.

An image uses a small part of the account’s weekly allowance: 0 to 1 percent on the free plan in our tests. See Weekly allowance.

https://api.useapi.net/v1/muse/jobs/image

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": "Restore the old photograph image_1 as a high-quality modern photo: remove the scratches, dust, stains, fading and grain, and colorize it naturally. Keep the exact same person, expression, pose, framing and crop, and the same portrait aspect ratio as image_1: do not crop, extend or reframe it.",
  "image_1": "user:12345-email:6578616d706c6540676d61696c2e636f6d-image:3e788737ea18422b9809aeb06a453c072e15f1b3fb219d7a4fd7e8fffd733d4f.jpg",
  "async": true,
  "replyUrl": "https://your-domain.com/webhook",
  "replyRef": "restore-1"
}
  • prompt is required, what to make. Name the inputs as image_1, image_2… and say the aspect ratio 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.
  • 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 image is ready. This one changed the dress in an image made by an earlier job, 34 seconds after the request.

    {
      "jobid": "j1012012322765813065i-u12345-email:[email protected]:a59c458f-efb4-4244-8ab7-f0d0c2744ea5-bot:muse",
      "type": "image",
      "email": "[email protected]",
      "status": "completed",
      "created": "2026-10-12T01:23:22.765Z",
      "request": {
        "prompt": "Change the dress of the woman in image_1 to a blue denim dress; keep everything else the same.",
        "image_1": "user:12345-email:6578616d706c6540676d61696c2e636f6d-image:media-generation-a-young-woman-in-a-red-summer-0-df6bcf46-d28c-4c0e-bffe-3b348d39cd0c.webp"
      },
      "chatId": "a59c458f-efb4-4244-8ab7-f0d0c2744ea5",
      "messageId": "dff54c7e-65d1-46ec-85d2-3c538bcbb0ca",
      "updated": "2026-10-12T01:23:56.496Z",
      "completed": "2026-10-12T01:23:56.496Z",
      "result": {
        "assets": [
          {
            "assetId": "user:12345-email:6578616d706c6540676d61696c2e636f6d-image:media-generation-change-the-dress-of-the-woman-0-0eec3b94-f75b-4eb5-a6cb-7c680546429e.webp",
            "type": "image",
            "mime": "image/webp",
            "bytes": 487120
          }
        ],
        "text": "Done — blue denim dress, everything else untouched."
      }
    }
    
  • 201 Created — async: true: the job is running. Fetch the result with GET /jobs/jobid, or wait for the webhook.

    {
      "jobid": "j1012012359487240036i-u12345-email:[email protected]:bcaa91f0-24e0-4b7e-8d2c-6624bf9562e0-bot:muse",
      "type": "image",
      "email": "[email protected]",
      "status": "started",
      "created": "2026-10-12T01:23:59.487Z",
      "request": {
        "prompt": "a red fox sitting in fresh snow, close-up photo",
        "async": true,
        "replyUrl": "https://your-domain.com/webhook",
        "replyRef": "fox-1"
      },
      "chatId": "bcaa91f0-24e0-4b7e-8d2c-6624bf9562e0",
      "replyUrl": "https://your-domain.com/webhook",
      "replyRef": "fox-1"
    }
    
  • 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 an image asset, 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 model not supported",
      "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 image: 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 image 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 image 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 image 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 image: 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: "image". 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/image" \
       -H "Content-Type: application/json" \
       -H "Authorization: Bearer …" \
       -d '{
         "prompt": "In exactly 16:9 aspect ratio (landscape), a cinematic photo of a vintage 1920s black BMW motorcycle parked on wet cobblestones at night in San Francisco, the Golden Gate Bridge glowing through fog in the distance"
       }'
    
  • const token = "API token";
    const apiUrl = "https://api.useapi.net/v1/muse";
    const headers = { "Content-Type": "application/json", "Authorization": `Bearer ${token}` };
    
    const response = await fetch(`${apiUrl}/jobs/image`, {
      method: "POST",
      headers,
      body: JSON.stringify({ prompt: "In exactly 9:16 aspect ratio, a full-body portrait photo of a woman biker in a black leather jacket in a rain-soaked neon alley" })
    });
    const job = await response.json();
    if (job.status === "completed") {
      const { assetId } = job.result.assets[0];
      const image = await fetch(`${apiUrl}/assets/${encodeURIComponent(assetId)}`, { headers });
      console.log("image bytes", (await image.arrayBuffer()).byteLength, job.result.text);
    } else
      console.log("response", response.status, job);
    
  • import requests
    from urllib.parse import quote
    
    token = "API token"
    apiUrl = "https://api.useapi.net/v1/muse"
    headers = {"Content-Type": "application/json", "Authorization": f"Bearer {token}"}
    
    data = {"prompt": "In exactly 9:16 aspect ratio, a full-body portrait photo of a woman biker in a black leather jacket in a rain-soaked neon alley"}
    job = requests.post(f"{apiUrl}/jobs/image", headers=headers, json=data).json()
    if job.get("status") == "completed":
        asset_id = job["result"]["assets"][0]["assetId"]
        image = requests.get(f"{apiUrl}/assets/{quote(asset_id, safe='')}", headers=headers)
        with open("image.webp", "wb") as f:
            f.write(image.content)
    else:
        print(job)
    
Try It
image_4 .. image_9