DEV Community

useapi.net
useapi.net

Posted on Originally published at useapi.net

How to Generate AI Images and Videos with the Muse API

Introduction

The useapi.net Muse API drives Muse, Meta's personal AI agent, from code: one POST makes an image or a 10-second video with spoken dialogue, and every file it makes is an asset id the next job can take as an input.

The API runs on your own Muse account, and the free plan works. This walkthrough takes three ordinary photos, a vintage BMW motorcycle, a red-haired rider and the beach below the Golden Gate Bridge, and turns them into one picture of her riding that motorcycle through the surf. The picture then becomes a video in which she shouts a line, and in a third job a different woman, taken from a fourth photo, replaces her in that video while the ride stays the same. Along the way it makes images from text, and it restores and colorizes a hundred-year-old portrait that then looks up and speaks. Every request below is the one that made the media on this page.

The rider from a photo, swapped into a clip Muse made from a still image (step 8).

What it cost

useapi.net is an API, not an image or video service of its own. Every call runs on a Muse account you add through Setup Muse, and uses that account's weekly Muse allowance. Muse reports it as a whole percent used, so a single image often shows no change. You pay useapi.net a flat $15/month for every API we offer, with no per-image or per-video charge.

What each step used on the free plan, taken from percentUsed before and after it, and how long each job took:

Step Endpoint Output Allowance used Time
Text to image, 9:16 POST /jobs/image 1152×2048 not measured 30 s
Text to image, exactly 16:9 POST /jobs/image 2048×1152 0% 21 s
Rider + motorcycle + beach POST /jobs/image 2048×1152 0% 31 s
The ride as a video POST /jobs/video 1280×720, 10 s 3% 88 s
Restore an old photo POST /jobs/image 1344×1792 1% 34 s
The portrait speaks POST /jobs/video 832×1104, 10 s 2% 68 s
Swap the rider POST /jobs/video 1248×704, 10 s 3% 138 s

Uploads use no allowance. On the free plan a video used 2 to 3 percent of the week and an image 0 to 1 percent, so one free account makes roughly 30 to 50 videos a week, or several hundred images (step 9).

Before you start

You need a useapi.net API token and a Muse account added through Setup Muse. Before you add the account:

  • Finish Muse's onboarding at muse.ai, including Meta's age check, and create one image there.
  • The account must have email + password sign-in, because the API signs in with the email and password. A Meta account that signs in only with an emailed code has no password yet: create one in Meta Accounts Center (Password and security), then add the account here. Without a password the add is refused with 400.
  • Use the account only for the API, with no other automation tools on it. Opening it at muse.ai in a browser is fine.

Every call is also in the Postman collection.

Also in Chinese: 中文教程. The whole walkthrough is also on GitHub as one script, in Node.js and Python: images-videos/ in useapi/muse-api, plus an agent skill for Claude Code and Codex.

The steps use curl, jq and these variables and helpers:

export USEAPI_TOKEN="user:12345-..."
export EMAIL="example@gmail.com"
export MUSE_PASSWORD="..."   # the Muse account password, used once in step 1
API=https://api.useapi.net/v1/muse
AUTH="Authorization: Bearer $USEAPI_TOKEN"
enc() { jq -rn --arg v "$1" '$v|@uri'; }
post() { curl -sS -X POST "$API/$1" -H "$AUTH" -H "Content-Type: application/json" -d "$2"; }
get() { curl -sS "$API/$1" -H "$AUTH"; }
upload() { curl -sS -X POST "$API/assets?email=$(enc "$EMAIL")" -H "$AUTH" -H "Content-Type: $2" --data-binary @"$1" | jq -r .assetId; }
download() { curl -sSf "$API/assets/$(enc "$1")" -H "$AUTH" -o "$2"; }

# run a job async: submit it, poll GET /jobs/{jobid} every 10 s, save the final job record to $1
run() {
  local job id
  job=$(post "$2" "$(jq '. + {async: true}' <<< "$3")")
  id=$(jq -r .jobid <<< "$job")
  while [ "$(jq -r .status <<< "$job")" = started ]; do
    sleep 10
    job=$(get "jobs/$(enc "$id")")
  done
  echo "$job" > "$1"
  jq '{status, error, text: .result.text, asset: .result.assets[0].assetId}' "$1"
}
Enter fullscreen mode Exit fullscreen mode

Every generation runs as a job. Without async, a POST waits up to about 100 seconds and returns the finished job record, or 202 with the job still running. A video here took 68 to 138 seconds, so run sends every job with async: true and polls GET /jobs/jobid. If run prints any status other than completed, read its error before going on.

To be called back instead of polling, add replyUrl: "https://example.com/hooks/muse" (and optionally replyRef) to any body below. The API then sends one POST of the final job record, as JSON, to that URL.

Ids contain : and @, so enc URL-encodes them whenever they go into a URL path. All inputs of one job must live on the same Muse account, so the uploads pin email, and a job runs on the account that holds its inputs.

How it was built

1. Add the account

POST /accounts takes the Muse account's email and password. The API signs in once, keeps the 30-day session and renews it itself. Muse emails the account a login code at each sign-in. That email is only a notice, nothing needs to be entered.

post accounts "$(jq -n --arg email "$EMAIL" --arg password "$MUSE_PASSWORD" '{email: $email, password: $password}')" | jq '{health, maxJobs, quota}'
Enter fullscreen mode Exit fullscreen mode

The answer shows health: "OK", maxJobs (2 by default) and the account's plan and usage, here tier: "HATCH_FREE", the free plan. If it answers 596 instead, Meta wants a security check: sign in once at muse.ai in a browser, complete it, and post the same email and password again.

2. Upload the photos

POST /assets takes the raw file as the body, up to 8.6 MB, and writes it into the account's Muse workspace. Nothing is stored at useapi.net. The answer's assetId is what a job takes as image_N.

BMW=$(upload bmw.jpeg image/jpeg)
RIDER=$(upload rider.jpeg image/jpeg)
BEACH=$(upload beach.jpeg image/jpeg)
OLD_PHOTO=$(upload old-photo.jpeg image/jpeg)
PINK=$(upload pink-hair.jpeg image/jpeg)
echo "$BMW"
# user:12345-email:6578616d706c6540676d61696c2e636f6d-image:7db53e7a1f0979cbab6ff46ec3f0a35b60e6ee579a0d738a9628cf46a0c7a0f5.jpg
Enter fullscreen mode Exit fullscreen mode

<code>image_1</code> — the motorcycle"></a><figcaption><code>image_1</code> — the motorcycle</figcaption><br>
<a href=<code>image_2</code> — the rider"></a><figcaption><code>image_2</code> — the rider</figcaption><br>
<a href=<code>image_3</code> — the beach"></a><figcaption><code>image_3</code> — the beach</figcaption><br>
<a href=the old portrait
the old portrait

the second rider
the second rider

The five inputs: the motorcycle, the rider, the beach, the old portrait and the second rider.

The asset id is built from the file's content, so uploading the same file to the same account again returns the same id and writes nothing.

3. Images from text

Muse has no aspect-ratio parameter: the prompt says it. A vertical portrait first:

run portrait.json jobs/image "$(jq -n '{prompt: "Vertical 9:16 full-body portrait photo: a confident young woman biker with long wavy fiery red hair, green eyes and freckles, in a worn black leather jacket, dark jeans and boots, standing in a rain-soaked neon alley at dusk, looking straight at the camera, cinematic lighting, photorealistic."}')"
download "$(jq -r '.result.assets[0].assetId' portrait.json)" portrait.webp
Enter fullscreen mode Exit fullscreen mode

Text to image at 9:16

open in new tab

That came back at exactly 9:16, 1152×2048. A landscape version asked as Cinematic 16:9 landscape photo: … came back 1920×1280, which is 3:2. Writing the ratio as in exactly 16:9 aspect ratio fixed it:

run bmw-noir.json jobs/image "$(jq -n '{prompt: "In exactly 16:9 aspect ratio (landscape), a cinematic photo of a vintage 1920s black BMW motorcycle with white pinstripes, spoked wheels and a brown leather saddle, parked on wet cobblestones at night in San Francisco, the Golden Gate Bridge glowing through fog in the distance, neo-noir moonlight, reflections on the wet street, shot on 35mm film."}')"
download "$(jq -r '.result.assets[0].assetId' bmw-noir.json)" bmw-noir.webp
Enter fullscreen mode Exit fullscreen mode

Text to image at exactly 16:9

open in new tab

Muse answered "That 16:9 frame with the bridge ghosting through the fog is pure neo-noir. Enjoy." Its reply is in result.text.

4. Three photos into one picture

Up to 9 images go into one job as image_1..image_9, and the prompt names them. Here the motorcycle is image_1, the rider image_2 and the beach image_3. The prompt asks Muse to use the photographed beach as it is, so the Golden Gate Bridge stays where it was in the photo.

run beach-ride.json jobs/image "$(jq -n --arg bmw "$BMW" --arg rider "$RIDER" --arg beach "$BEACH" \
  '{image_1: $bmw, image_2: $rider, image_3: $beach,
  prompt: "In exactly 16:9 aspect ratio (landscape), a cinematic photo: the woman from image_2 (her face, long wavy red hair, light blue top and jeans) riding the exact vintage motorcycle from image_1 along the shoreline of the beach in image_3, the front wheel cutting through the shallow surf and throwing spray, bright afternoon sun, her hair streaming in the wind, photorealistic. Use the beach, rocks and Golden Gate Bridge from image_3 as the background exactly as photographed: same position, same orientation, not mirrored or redrawn. Keep her face and the motorcycle'\''s design exactly as in the images."}')"
BEACH_RIDE=$(jq -r '.result.assets[0].assetId' beach-ride.json)
download "$BEACH_RIDE" beach-ride.webp
Enter fullscreen mode Exit fullscreen mode

The rider, the motorcycle and the beach in one picture

open in new tab

31 seconds, and no visible change in the weekly allowance.

5. The picture as a video, no re-upload

BEACH_RIDE is the id of a file Muse made. It is already in the account's workspace, so it goes straight into the next job as image_1, with no download and no upload. Animate image_1 makes a clip that starts from the picture and keeps its shape, and the line in quotes is spoken.

run surf.json jobs/video "$(jq -n --arg img "$BEACH_RIDE" '{image_1: $img,
  prompt: "Animate image_1 into a cinematic video in exactly 16:9 aspect ratio (landscape): she rides the vintage motorcycle along the shoreline, the front wheel slices through the shallow waves and sends spray flying, the old engine roars, she raises one fist and shouts with a laugh: \"Nothing beats this view!\" The camera tracks alongside her. The beach, the rocks and the Golden Gate Bridge stay exactly as in image_1 (same orientation). Keep her face and the motorcycle exactly as in image_1."}')"
SURF=$(jq -r '.result.assets[0].assetId' surf.json)
download "$SURF" surf.mp4
Enter fullscreen mode Exit fullscreen mode

10 seconds at 1280×720 with sound. Muse said "Done. 10 seconds, 16:9 landscape, straight from your image — no edits, no upscaling."

The first try of this exact job failed after 37 seconds: Muse answered that "the video worker glitched out before it rendered anything, so no clip was made". The API reports an answer like that as 503, nothing made, and the same request worked when run again. A refusal on content grounds is a 403 instead, and needs a different prompt.

6. Restore and colorize an old photo

An image job can also repair an image. The old portrait is a faded sepia photo of 685×900 pixels, 3:4. The prompt lists what to fix and insists on the same frame.

run restored.json jobs/image "$(jq -n --arg img "$OLD_PHOTO" '{image_1: $img,
  prompt: "Restore the old photograph image_1 as a high-quality modern photo: remove the scratches, dust, stains, fading and grain, recover sharp detail in the face, hair and fabric, and colorize it naturally (warm natural skin tones, dark brown hair, ivory tulle shawl, a soft pink rose with green leaves, a muted warm background). Keep the exact same person, expression, pose, framing and crop, and the same portrait aspect ratio as image_1 (3:4, 685×900): do not crop, extend or reframe it."}')"
RESTORED=$(jq -r '.result.assets[0].assetId' restored.json)
download "$RESTORED" restored.webp
Enter fullscreen mode Exit fullscreen mode

Before: the original, 685×900
Before: the original, 685×900

After: restored and colorized, 1344×1792
After: restored and colorized, 1344×1792

The result is 1344×1792, the same 3:4 frame at about twice the size, with the same pose and crop. Muse said "Done — restored, de-grained, and colorized, same framing and pose."

7. The portrait comes to life

The restored image goes straight into a video job. The clip keeps the portrait's shape, so it came back 832×1104.

run portrait-video.json jobs/video "$(jq -n --arg img "$RESTORED" '{image_1: $img,
  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.\" A light breeze moves a few strands of her hair, soft window light, the camera stays still with a very slow push-in. Keep her face exactly as in image_1."}')"
download "$(jq -r '.result.assets[0].assetId' portrait-video.json)" portrait.mp4
Enter fullscreen mode Exit fullscreen mode

8. Swap the rider in the video

A video job also takes videos. video_1 is the surf clip from step 5, and image_1 is the photo of a second woman with pink hair in two buns. The prompt asks Muse to replace the rider and keep everything else, and gives her a new line.

run swap.json jobs/video "$(jq -n --arg vid "$SURF" --arg img "$PINK" '{video_1: $vid, image_1: $img,
  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."}')"
download "$(jq -r '.result.assets[0].assetId' swap.json)" swap.mp4
Enter fullscreen mode Exit fullscreen mode

Before: the original rider

After: the rider from the photo

At 138 seconds this was the slowest job of the walkthrough, which is why every job here runs async. The clip is the same ride with the other woman on the motorcycle, shouting the new line. Muse said "Done. She's in, 10 seconds, 16:9, same ride, same bridge, same waves."

9. Check the weekly allowance

GET /accounts/email reads the account's plan and usage live:

get "accounts/$(enc "$EMAIL")" | jq '{session, quota}'
Enter fullscreen mode Exit fullscreen mode

quota.percentUsed is the share of the week used, in whole percent, and quota.resetsAt is when it starts over, a week after it began. The three videos above used 2 to 3 percent each and the images 0 or 1. Over the whole test session, which also ran two jobs not shown here, the free account went from 16 to 28 percent. When an account runs out, new jobs without email go to your other accounts, and a job pinned to it answers 412 until resetsAt.

What we learned

  • Write the aspect ratio as in exactly 16:9 aspect ratio. Cinematic 16:9 landscape photo came back 3:2 once.
  • Every video is about 10 seconds. A prompt asking for 5 seconds still got 10.
  • An image-to-video clip keeps the shape of its image unless the prompt asks for another one: in exactly 9:16 aspect ratio turned our 16:9 beach picture into a vertical 720×1280 clip, re-framed rather than stretched.
  • Lines written in quotes in a video prompt are spoken.
  • Pass generated files on by assetId. They stay in the account's workspace, so a chain of edits needs no downloads in between.
  • Say what must stay the same. "Same position, same orientation, not mirrored or redrawn" kept the real beach in the composite.
  • A 503 with Muse's own words such as "glitched out" is worth running again. A 403 is a refusal and needs a different prompt.
  • Send videos with async: true. A sync request waits about 100 seconds, and the swap took 138.

Frequently asked questions

Is there an API for Meta Muse (muse.ai)? useapi.net offers one: the Muse API runs Muse on your own account. You add the account with its email and password, then make images with POST /jobs/image and videos with POST /jobs/video.

Does it work on the free Muse plan? Yes. Everything on this page was made on the free plan (HATCH_FREE), which has a weekly allowance. A video used 2 to 3 percent of it and an image 0 to 1 percent.

How do I set the aspect ratio, length or resolution? In the prompt. There are no parameters for them. The aspect ratio is followed when written as in exactly 16:9 aspect ratio. Videos are always about 10 seconds at about 720p, with sound.

Can I use my own photos and videos? Yes. Upload them with POST /assets, up to 8.6 MB each, and pass the assetId as image_1..image_9; video jobs also take video_1..video_3 and audio_1..audio_3. All inputs of one job must be on the same account.

How do I make a photo talk? Pass it as image_1 to POST /jobs/video with a prompt like Animate image_1: … she says softly: "…", as in step 7.

Can it replace a person in a video? Yes. Pass the clip as video_1 and a photo of the new person as image_1, and ask Muse to replace the person and keep everything else, as in step 8.

Why did my sync request return 202? The job was still running after about 100 seconds. It keeps running on our side. Fetch it with GET /jobs/jobid, or send long jobs with async: true.

Why did Muse email me a login code? The API signed in to the account, which happens when you add it and when the session is renewed, about once every 30 days. The code needs no action. See Setup Muse.

Conclusion

Visit our Discord Server or Telegram Channel for any support questions and concerns.

Top comments (0)