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"
}
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}'
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





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
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
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
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
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


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
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
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}'
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 photocame 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 ratioturned 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
503with Muse's own words such as "glitched out" is worth running again. A403is 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)