=== useapi.net — universal note === Generated: 2026-10-12 07:13 UTC Authentication (applies to every useapi.net API). Header: Authorization: Bearer user:- Use the COMPLETE token, including the `user:` prefix and the alphanumeric suffix. Do not truncate. Do not URL-encode. A single token authorizes every API under the user's subscription. Service-specific patterns. Identifier names (jobid / taskId / musicId / etc.), job lifecycle, response shapes, webhook semantics, and synchronous-vs-async behavior vary PER API. Use ONLY the service-specific documentation below to determine the correct request body, response shape, polling endpoint, and status values for THIS API. Do not assume conventions from another useapi.net API carry over. For cross-service context (e.g. which APIs expose the same underlying model, billing tiers, model availability matrix), see https://useapi.net/llms.txt === END universal note === === URL: https://useapi.net/docs/start-here/setup-muse === Document URL: https://useapi.net/docs/start-here/setup-muse --- layout: default title: Setup Muse description: "How to add a Meta Muse (muse.ai) account to the useapi.net Muse API with its email and password, what the login-code email means, and Meta's security check." parent: Start Here nav_order: 209 permalink: /docs/start-here/setup-muse --- # Setup Muse October 12, 2026 useapi.net is a third-party API, not affiliated with or endorsed by Meta. ## Table of contents Approximately 5 minutes to complete setup steps. --- > This is the setup guide for [Muse API](/docs/api-muse-v1). A [Muse](https://muse.ai) account and a [useapi.net subscription](/docs/subscription) are required for the API to work. Add a Muse account with its email and password. The API signs in once and keeps the account signed in from then on. ### Before you start - Muse is available only in the US and Canada ([Meta](https://about.fb.com/news/2026/09/introducing-muse-small-business/)), so create the account there. Once it is added, you can call the API from anywhere. - Finish Muse's onboarding at [muse.ai](https://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](https://accountscenter.meta.com/password_and_security/) (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. ![The Muse sign-in page at muse.ai](/assets/images/muse-setup-1.png) ### Add the account
A new account answers `201`, a re-added one `200`, with `health: "OK"`, `sessionExpiresAt` and `quota`. Each account runs 2 jobs at a time unless you set `maxJobs` (1 to 5) above. To change it later, post the email with a new `maxJobs` and no password to [POST /accounts](/docs/api-muse-v1/post-muse-accounts). ### What to expect - The API renews the session itself: before the 30-day session expires, and whenever Muse asks for a new sign-in. You never re-add the account for that. - Muse emails the account a login code on each sign-in. Nothing needs to be done with it. - Re-adding the account with the same password reuses its live session, so it does not sign in again. ### If the account needs attention The API pauses an account it cannot use, emails you once, and answers `596` with the reason, also shown in `health` on [GET /accounts](/docs/api-muse-v1/get-muse-accounts): - Meta asks for a security check, usually after a sign-in from a new place. Sign in at [muse.ai](https://muse.ai) in a browser, complete the check, then add the account again with the same email and password. - Muse refused to sign in when the session was renewed, usually because the password changed. Add the account again with the current password. Temporary problems never pause an account. They answer `503`, and a retry works. ### Usage limits Muse meters each account weekly. [GET /accounts/`email`](/docs/api-muse-v1/get-muse-accounts-email) returns the plan and usage in `quota`: `tier`, `percentUsed`, `resetsAt` and `quotaStatus`. | Plan | Price | Weekly allowance | |---|---|---| | Free | $0 | not published | | Power | $20/month | 500 million Muse tokens | | Maximum | $100/month | 3 billion Muse tokens | Source: Meta, [About Muse subscriptions](https://www.meta.com/help/subscriptions/1021145227643680/), October 2026. Availability can differ by region. - On the free plan, a video used 2 to 3 percent of the weekly allowance in our tests and an image 0 to 1 percent. Meta does not publish per-job token costs. - The allowance resets at `resetsAt`. Once it is used up, new jobs go to your other accounts, and a job pinned to the account answers `412`. === URL: https://useapi.net/docs/api-muse-v1/delete-muse-accounts-email === Document URL: https://useapi.net/docs/api-muse-v1/delete-muse-accounts-email --- layout: default title: DELETE accounts/`email` description: "Remove a Muse account from the useapi.net Muse API via DELETE accounts/email. Its stored password and session are deleted, while its muse.ai files and chats stay." parent: Muse API v1 nav_order: 140 permalink: /docs/api-muse-v1/delete-muse-accounts-email --- ## Delete a Muse account October 12, 2026 --- Remove an added Muse account from your useapi.net account. The stored password and session are deleted and the account slot is freed. Nothing is deleted on Muse's side. The account's files and chats stay in its Muse workspace, and you can add the account again at any time with [POST /accounts](/docs/api-muse-v1/post-muse-accounts). Its asset ids, and the chat history of its jobs, stop working until it is added again. Job records stay readable with [GET /jobs/`jobid`](/docs/api-muse-v1/get-muse-jobs-jobid). > **https://api.useapi.net/v1/muse/accounts/`email`** - `email` is the email of an added account, URL-encoded in the path. ##### Request Headers ``` yaml Authorization: Bearer {API token} ``` - `API token` is **required**, see [Setup useapi.net](/docs/start-here/setup-useapi) for details. ##### Responses **204** **204 No Content** — the account was removed. The response body is empty. **400** **400 Bad Request** — the `email` in the path is not a valid email, or no account is added at all. ```json { "error": "Path parameter email (not-an-email) not a valid email", "code": 400 } ``` **401** **401 Unauthorized** Invalid API token. ```json { "error": "useapi.net ⁝ Unauthorized", "code": 401 } ``` **404** **404 Not Found** — no account is added for this `email`. ```json { "error": "Account example@gmail.com not found", "code": 404 } ``` ##### Model `204` has no body. Errors return: ```typescript { // TypeScript, all fields are optional error: string code: number } ``` ##### Examples **Curl** ``` bash curl -X DELETE "https://api.useapi.net/v1/muse/accounts/example%40gmail.com" \ -H "Authorization: Bearer …" ``` **JavaScript** ``` javascript const token = "API token"; const email = "Previously added account email"; const apiUrl = `https://api.useapi.net/v1/muse/accounts/${encodeURIComponent(email)}`; const response = await fetch(apiUrl, { method: "DELETE", headers: { "Authorization": `Bearer ${token}`, }, }); console.log("response", response.status); ``` **Python** ``` python import requests from urllib.parse import quote token = "API token" email = "Previously added account email" apiUrl = f"https://api.useapi.net/v1/muse/accounts/{quote(email, safe='')}" headers = { "Authorization" : f"Bearer {token}" } response = requests.delete(apiUrl, headers=headers) print(response.status_code) ``` === URL: https://useapi.net/docs/api-muse-v1/get-muse-accounts-email === Document URL: https://useapi.net/docs/api-muse-v1/get-muse-accounts-email --- layout: default title: GET accounts/`email` description: "Read one Muse account live via GET accounts/email in the useapi.net Muse API: its plan tier, the percent of the weekly allowance used and the reset time." parent: Muse API v1 nav_order: 120 permalink: /docs/api-muse-v1/get-muse-accounts-email --- ## Retrieve a Muse account October 12, 2026 --- Get one added account with a live read from Muse: its plan and how much of the weekly allowance it has used. The read uses the stored session, as a job does, and every call goes to Muse. The result is also saved, so [GET /accounts](/docs/api-muse-v1/get-muse-accounts) and the choice of account for new jobs use it. Muse meters each account weekly. [GET /accounts/`email`](/docs/api-muse-v1/get-muse-accounts-email) returns the plan and usage in `quota`: `tier`, `percentUsed`, `resetsAt` and `quotaStatus`. | Plan | Price | Weekly allowance | |---|---|---| | Free | $0 | not published | | Power | $20/month | 500 million Muse tokens | | Maximum | $100/month | 3 billion Muse tokens | Source: Meta, [About Muse subscriptions](https://www.meta.com/help/subscriptions/1021145227643680/), October 2026. Availability can differ by region. - On the free plan, a video used 2 to 3 percent of the weekly allowance in our tests and an image 0 to 1 percent. Meta does not publish per-job token costs. - The allowance resets at `resetsAt`. Once it is used up, new jobs go to your other accounts, and a job pinned to the account answers `412`. If Muse no longer accepts the session, the API signs the account in again once, the same as on any other request, and then reads it. A paused account (one whose `health` is not `OK`) is returned as stored, without a call to Muse. > **https://api.useapi.net/v1/muse/accounts/`email`** - `email` is the email of an added account, URL-encoded in the path. ##### Request Headers ``` yaml Authorization: Bearer {API token} ``` - `API token` is **required**, see [Setup useapi.net](/docs/start-here/setup-useapi) for details. ##### Responses **200** **200 OK** ```json { "email": "example@gmail.com", "maxJobs": 3, "added": "2026-10-12T00:34:19.139Z", "updated": "2026-10-12T01:29:14.234Z", "sessionExpiresAt": "2026-11-11T00:34:18.820Z", "quota": { "tier": "HATCH_FREE", "percentUsed": 3, "resetsAt": "2026-10-19T01:13:18.000Z", "quotaStatus": "SUFFICIENT", "topupBalance": 0, "readAt": "2026-10-12T01:29:14.794Z" }, "health": "OK", "session": "signed-in" } ``` - `session` is `signed-in` after a live read. It is `checkpoint` when Meta asks for a security check on the account (the account is paused from then on and `health` says what to do), and `unknown` when Muse did not answer the read: `quotaError` then says why and `quota` is the last saved one. - A paused account is returned as stored, without `session`. **400** **400 Bad Request** — the `email` in the path is not a valid email. ```json { "error": "Path parameter email (not-an-email) not a valid email", "code": 400 } ``` **401** **401 Unauthorized** Invalid API token. ```json { "error": "useapi.net ⁝ Unauthorized", "code": 401 } ``` **404** **404 Not Found** — no account is added for this `email`. ```json { "error": "Account example@gmail.com not found", "code": 404 } ``` **503** **503 Service Unavailable** — Muse did not accept the session and signing in again could not finish right now. Retry in a few minutes. ```json { "error": "Muse refused account example@gmail.com's new session, please retry in a few minutes", "code": 503 } ``` **596** **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](https://muse.ai) in a browser, complete the check, then add the account again with [POST /accounts](/docs/api-muse-v1/post-muse-accounts) using the same email and password. The API keeps using its session, so no new sign-in is needed. ```json { "error": "Account example@gmail.com: Meta asks for a security check on account example@gmail.com (\"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. ```json { "error": "Account example@gmail.com: Muse refused to sign in account example@gmail.com (…). Update its password with POST /accounts, or sign in at https://muse.ai to clear the block, then re-add it", "code": 596 } ``` ##### Model ```typescript { // TypeScript, all fields are optional email: string maxJobs: number // jobs this account may run at the same time added: string // ISO 8601, when the account was first added updated?: string // ISO 8601, the last re-add or maxJobs change sessionExpiresAt: string // ISO 8601, when the Muse session expires (30 days, renewed by the API) quota?: { // the account's Muse plan and weekly usage, as last read tier: string | null // Muse plan, e.g. HATCH_FREE percentUsed: number | null // share of the weekly allowance used, in whole percent resetsAt: string | null // ISO 8601, when the weekly allowance resets quotaStatus: string | null // SUFFICIENT while jobs may run topupBalance: number | null readAt: string // ISO 8601, when this was read from Muse } health: string // 'OK', or why the account needs attention (596) session?: 'signed-in' | 'checkpoint' | 'unknown' quotaError?: string // session 'unknown': why Muse did not answer the read error?: string // errors only code?: number // errors only } ``` ##### Examples **Curl** ``` bash curl "https://api.useapi.net/v1/muse/accounts/example%40gmail.com" \ -H "Authorization: Bearer …" ``` **JavaScript** ``` javascript const token = "API token"; const email = "Previously added account email"; const apiUrl = `https://api.useapi.net/v1/muse/accounts/${encodeURIComponent(email)}`; const response = await fetch(apiUrl, { headers: { "Authorization": `Bearer ${token}`, }, }); const result = await response.json(); console.log("response", {response, result}); ``` **Python** ``` python import requests from urllib.parse import quote token = "API token" email = "Previously added account email" apiUrl = f"https://api.useapi.net/v1/muse/accounts/{quote(email, safe='')}" headers = { "Authorization" : f"Bearer {token}" } response = requests.get(apiUrl, headers=headers) print(response, response.json()) ``` === URL: https://useapi.net/docs/api-muse-v1/get-muse-accounts === Document URL: https://useapi.net/docs/api-muse-v1/get-muse-accounts --- layout: default title: GET accounts description: "List every Muse account added to the useapi.net Muse API with GET accounts: health, maxJobs, session expiry and the weekly usage as last read, without calling Muse." parent: Muse API v1 nav_order: 110 permalink: /docs/api-muse-v1/get-muse-accounts --- ## List Muse accounts October 12, 2026 --- List the Muse accounts added with [POST /accounts](/docs/api-muse-v1/post-muse-accounts). Each entry shows the account's `health`, its `maxJobs`, when its session expires, and its plan and weekly usage as last read. This endpoint does not call Muse. For the live plan and usage use [GET /accounts/`email`](/docs/api-muse-v1/get-muse-accounts-email). [GET /jobs](/docs/api-muse-v1/get-muse-jobs) shows how many jobs each account is running. > **https://api.useapi.net/v1/muse/accounts** ##### Request Headers ``` yaml Authorization: Bearer {API token} ``` - `API token` is **required**, see [Setup useapi.net](/docs/start-here/setup-useapi) for details. ##### Responses **200** **200 OK** ```json { "example@gmail.com": { "email": "example@gmail.com", "maxJobs": 3, "added": "2026-10-12T00:34:19.139Z", "updated": "2026-10-12T01:29:14.234Z", "sessionExpiresAt": "2026-11-11T00:34:18.820Z", "quota": { "tier": "HATCH_FREE", "percentUsed": 0, "resetsAt": "2026-10-19T01:13:05.000Z", "quotaStatus": "SUFFICIENT", "topupBalance": 0, "readAt": "2026-10-12T01:13:05.614Z" }, "health": "OK" } } ``` - The response is an object keyed by account email, or `{}` when no account is added. - `health` is `OK`, or the reason the account needs attention. Such an account is paused: it takes no new jobs until you add it again with [POST /accounts](/docs/api-muse-v1/post-muse-accounts). See [Setup Muse](/docs/start-here/setup-muse#if-the-account-needs-attention). - `quota` is updated when the account is added, on every [GET /accounts/`email`](/docs/api-muse-v1/get-muse-accounts-email), and when Muse refuses a job. **401** **401 Unauthorized** Invalid API token. ```json { "error": "useapi.net ⁝ Unauthorized", "code": 401 } ``` ##### Model ```typescript { // TypeScript, all fields are optional [email: string]: { email: string maxJobs: number // jobs this account may run at the same time added: string // ISO 8601, when the account was first added updated?: string // ISO 8601, the last re-add or maxJobs change sessionExpiresAt: string // ISO 8601, when the Muse session expires (30 days, renewed by the API) quota?: { // the account's Muse plan and weekly usage, as last read tier: string | null // Muse plan, e.g. HATCH_FREE percentUsed: number | null // share of the weekly allowance used, in whole percent resetsAt: string | null // ISO 8601, when the weekly allowance resets quotaStatus: string | null // SUFFICIENT while jobs may run topupBalance: number | null readAt: string // ISO 8601, when this was read from Muse } health: string // 'OK', or why the account needs attention (596) } } ``` ##### Examples **Curl** ``` bash curl "https://api.useapi.net/v1/muse/accounts" \ -H "Authorization: Bearer …" ``` **JavaScript** ``` javascript const token = "API token"; const apiUrl = "https://api.useapi.net/v1/muse/accounts"; const response = await fetch(apiUrl, { headers: { "Authorization": `Bearer ${token}`, }, }); const result = await response.json(); console.log("response", {response, result}); ``` **Python** ``` python import requests token = "API token" apiUrl = "https://api.useapi.net/v1/muse/accounts" headers = { "Authorization" : f"Bearer {token}" } response = requests.get(apiUrl, headers=headers) print(response, response.json()) ``` === URL: https://useapi.net/docs/api-muse-v1/get-muse-assets-assetId === Document URL: https://useapi.net/docs/api-muse-v1/get-muse-assets-assetId --- layout: default title: GET assets/`assetId` description: "Download an uploaded or generated Muse file via GET assets/assetId in the useapi.net Muse API: WebP image, MP4 video or audio, streamed from the workspace." parent: Muse API v1 nav_order: 310 permalink: /docs/api-muse-v1/get-muse-assets-assetId --- ## Download an asset October 12, 2026 --- Download a file by its `assetId`: an image or video Muse made, from a finished job's `result.assets[]`, or a file you uploaded with [POST /assets](/docs/api-muse-v1/post-muse-assets). The response body is the file itself, streamed from the Muse workspace of the account that holds it on each request. No copy is kept at useapi.net. The API never deletes files: they stay in the account's Muse workspace. A file that is no longer there, for example because it was deleted in the muse.ai app, answers `404`. > **https://api.useapi.net/v1/muse/assets/`assetId`** - `assetId` is URL-encoded in the path (it contains `:`), for example `user%3A12345-email%3A6578616d706c6540676d61696c2e636f6d-image%3Ad62d3099…jpg`. ##### Request Headers ``` yaml Authorization: Bearer {API token} ``` - `API token` is **required**, see [Setup useapi.net](/docs/start-here/setup-useapi) for details. ##### Response headers - `Content-Type`: the file's type, for example `image/webp` for a generated image or `video/mp4` for a video. - `Content-Disposition`: `inline; filename=""`, the name of the file in the workspace. - `Content-Length`: when Muse sends the size. ##### Responses **200** **200 OK** The raw file bytes (not JSON), for example: ``` Content-Type: video/mp4 Content-Length: 3753558 Content-Disposition: inline; filename="media-generation-restored-portrait-animation-0-4322afa2-10d0-47b0-acc6-675a129aa78e.mp4" ``` **400** **400 Bad Request** The path value is not a Muse `assetId`. ```json { "error": "Path parameter assetId (…) is not a valid Muse asset id (from POST /assets or a job result)", "code": 400 } ``` **401** **401 Unauthorized** Invalid API token. ```json { "error": "useapi.net ⁝ Unauthorized", "code": 401 } ``` **403** **403 Forbidden** The `assetId` was issued to a different API token. ```json { "error": "This asset id does not belong to this API token", "code": 403 } ``` **404** **404 Not Found** The file is no longer in the account's Muse workspace, or the account that holds it is not added. ```json { "error": "This file is no longer in the Muse workspace of account example@gmail.com", "code": 404 } ``` ```json { "error": "Account example@gmail.com (from the asset id) is not configured", "code": 404 } ``` **502** **502 Bad Gateway** Muse did not send the file. Retry shortly. ```json { "error": "Muse did not send the file (…), please retry", "code": 502 } ``` **503** **503 Service Unavailable** Muse could not be reached. Retry shortly. ```json { "error": "Could not connect to the Muse workspace of account example@gmail.com (…), please retry shortly", "code": 503 } ``` **596** **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](https://muse.ai) in a browser, complete the check, then add the account again with [POST /accounts](/docs/api-muse-v1/post-muse-accounts) using the same email and password. The API keeps using its session, so no new sign-in is needed. ```json { "error": "Account example@gmail.com: Meta asks for a security check on account example@gmail.com (\"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. ```json { "error": "Account example@gmail.com: Muse refused to sign in account example@gmail.com (…). Update its password with POST /accounts, or sign in at https://muse.ai to clear the block, then re-add it", "code": 596 } ``` ##### Model A successful response is the file itself (not JSON), with the headers listed above. Errors return: ```typescript { // TypeScript, all fields are optional error: string code: number } ``` ##### Examples **Curl** ``` bash ASSETID="user:12345-email:6578616d706c6540676d61696c2e636f6d-video:media-generation-restored-portrait-animation-0-4322afa2-10d0-47b0-acc6-675a129aa78e.mp4" curl -H "Authorization: Bearer …" \ "https://api.useapi.net/v1/muse/assets/$(python3 -c "import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1],safe=''))" "$ASSETID")" \ --output video.mp4 ``` **JavaScript** ``` javascript const token = "API token"; const assetId = "assetId from a finished job or POST /assets"; const apiUrl = `https://api.useapi.net/v1/muse/assets/${encodeURIComponent(assetId)}`; const response = await fetch(apiUrl, { headers: { "Authorization": `Bearer ${token}`, }, }); if (!response.ok) console.log("error", response.status, await response.json()); else { const file = await response.arrayBuffer(); console.log("downloaded", file.byteLength, response.headers.get("content-type")); } ``` **Python** ``` python import requests from urllib.parse import quote token = "API token" assetId = "assetId from a finished job or POST /assets" apiUrl = "https://api.useapi.net/v1/muse/assets/" + quote(assetId, safe="") headers = { "Authorization" : f"Bearer {token}" } with requests.get(apiUrl, headers=headers, stream=True) as response: response.raise_for_status() with open("video.mp4", "wb") as f: for chunk in response.iter_content(chunk_size=1 << 20): f.write(chunk) ``` === URL: https://useapi.net/docs/api-muse-v1/get-muse-jobs-jobid === Document URL: https://useapi.net/docs/api-muse-v1/get-muse-jobs-jobid --- layout: default title: GET jobs/`jobid` description: "Poll a Muse image or video job via GET jobs/jobid in the useapi.net Muse API: status, generated asset ids, the agent's reply, error codes, chat on request." parent: Muse API v1 nav_order: 510 permalink: /docs/api-muse-v1/get-muse-jobs-jobid --- ## Retrieve a job October 12, 2026 --- Fetch a job record by its `jobid`. [POST /jobs/image](/docs/api-muse-v1/post-muse-jobs-image) and [POST /jobs/video](/docs/api-muse-v1/post-muse-jobs-video) both return one. After an async request, poll this endpoint until `status` is `completed` or `failed`. An image takes 20 to 60 seconds and a video 60 to 140, so polling every 10 seconds is enough. Our side follows each job for up to 10 minutes. If a job is still running well past its usual time, reading it also checks the job's Muse chat again, so a finished result is not lost. A job that has no result 15 minutes after it was created is failed with `504`. Instead of polling, pass `replyUrl` (and optionally `replyRef`) with the POST: when the job completes or fails, the API sends one `POST` of this same job record, as JSON, to that URL. See [Model](#model). Job records are kept for 7 days. After that the job answers `410`. > **https://api.useapi.net/v1/muse/jobs/`jobid`?history=`history`** - `jobid` is URL-encoded in the path (it contains `:` and `@`), for example `j1012014112831908109v-u12345-email%3Aexample%40gmail.com-task%3A3e8bc834-f243-4172-8f6e-9fc28e1a107c-bot%3Amuse`. ##### Request Headers ``` yaml Authorization: Bearer {API token} ``` - `API token` is **required**, see [Setup useapi.net](/docs/start-here/setup-useapi) for details. ##### Query Parameters - `history` is optional, `true` to add `history`: the job's turn read live from its Muse chat (your message, the agent's replies and the files it showed), for troubleshooting. It opens a connection to Muse, so it is slower than a plain poll. Use it to see what the agent said, or, after a `504`, whether it made the file after all. Links into the Muse workspace and token-like fields are left out. The shape of `history` is Muse's own and can change. When the chat cannot be read, `historyError` says why. Default: `false`. ##### Responses **200** **200 OK** Completed (an old portrait, restored by an earlier job, brought to life): ```json { "jobid": "j1012014112831908109v-u12345-email:example@gmail.com-task:3e8bc834-f243-4172-8f6e-9fc28e1a107c-bot:muse", "type": "video", "email": "example@gmail.com", "status": "completed", "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.\" 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.", "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", "messageId": "1df531ad-e133-4725-b706-986ab4d19714", "updated": "2026-10-12T01:42:21.321Z", "completed": "2026-10-12T01:42:21.321Z", "result": { "assets": [ { "assetId": "user:12345-email:6578616d706c6540676d61696c2e636f6d-video:media-generation-restored-portrait-animation-0-4322afa2-10d0-47b0-acc6-675a129aa78e.mp4", "type": "video", "mime": "video/mp4", "bytes": 3753558 } ], "text": "Here it is." } } ``` Running: ```json { "jobid": "j1012014112831908109v-u12345-email:example@gmail.com-task:3e8bc834-f243-4172-8f6e-9fc28e1a107c-bot:muse", "type": "video", "email": "example@gmail.com", "status": "started", "created": "2026-10-12T01:41:12.831Z", "request": { "prompt": "Animate image_1 into a video with the same portrait frame: …", "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" } ``` Failed (Muse could not make the video this time and said why, so it can simply run again): ```json { "jobid": "j1012014437650921837v-u12345-email:example@gmail.com-task:3c0c6fd6-a99f-4d7f-bfd5-91e6af964242-bot:muse", "type": "video", "email": "example@gmail.com", "status": "failed", "created": "2026-10-12T01:44:37.650Z", "request": { "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, …", "image_1": "user:12345-email:6578616d706c6540676d61696c2e636f6d-image:media-generation-beach-shoreline-motorcycle-rid-0-99c2e513-813a-4707-9f5b-f7e68780b072.webp", "async": true }, "chatId": "3c0c6fd6-a99f-4d7f-bfd5-91e6af964242", "messageId": "f8ac44d6-9b29-455a-8fb7-df4a20c2b82a", "updated": "2026-10-12T01:45:14.352Z", "completed": "2026-10-12T01:45:14.352Z", "error": { "code": 503, "message": "Muse failed to make it this time, nothing was made: I tried to kick off your shoreline ride video and the video worker glitched out before it rendered anything, so no clip was made. I can't retry it in this same go.\n\nSay the word and I'll run it again from image_1 in 16:9.. Please retry" } } ``` With `?history=true`, the same record plus `history`, abridged here (an image job): ```json { "jobid": "j1012011314385546947i-u12345-email:example@gmail.com-task:43d100f9-1dff-4b00-a223-453b45b8d3e3-bot:muse", "status": "completed", "…": "…", "history": [ { "event_name": "message.user", "role": "user", "display_text": "Create one image: a young woman in a red summer dress on a beach at golden hour, full body, vertical 9:16 photo\n\nMake it directly. Do not ask me questions, do not upscale, crop or edit the result afterwards.", "…": "…" }, { "event_name": "message.assistant", "role": "assistant", "display_text": "Done.", "…": "…" }, { "event_name": "delta.presentation", "role": "assistant", "display_text": "Red summer dress at golden hour", "…": "…" } ] } ``` The first event shows the message the API sent: your prompt after `Create one image:` (or `Create one video:`), with the line that asks Muse to make the file directly. With inputs, a list naming each input's file in the workspace comes first. **400** **400 Bad Request** The path value is not a Muse `jobid`, or a query parameter is not supported. ```json { "error": "Path parameter jobid (…) is not a valid Muse job id", "code": 400 } ``` **401** **401 Unauthorized** Invalid API token. ```json { "error": "useapi.net ⁝ Unauthorized", "code": 401 } ``` **403** **403 Forbidden** ```json { "error": "This job does not belong to this API token", "code": 403 } ``` **404** **404 Not Found** No such job. ```json { "error": "Job not found", "code": 404 } ``` **410** **410 Gone** The job is older than 7 days: job records are kept for 7 days. ```json { "error": "Job has expired", "code": 410 } ``` ##### Model The job record. [POST /jobs/image](/docs/api-muse-v1/post-muse-jobs-image) and [POST /jobs/video](/docs/api-muse-v1/post-muse-jobs-video) return it too, and it is the body of the `replyUrl` webhook. ```typescript { // TypeScript, all fields are optional jobid: string // j-u-email:-task:-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 // 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 } ``` | `status` | Meaning | |---|---| | `started` | Muse is working on it. Every job starts here. | | `completed` | Done. Each file in `result.assets[]` downloads with [GET /assets/`assetId`](/docs/api-muse-v1/get-muse-assets-assetId) and can be the input of the next job. | | `failed` | Done without a file. `error` says why. | ### Error codes | `error.code` | Meaning | |---|---| | `403` | Muse refused to make it, for example on content grounds, and `message` has the agent's own words. Running the same prompt again will not help: change the prompt or the inputs. `403` is also the answer when Muse asked for a confirmation instead of making the file. | | `404` | The account was removed with [DELETE /accounts/`email`](/docs/api-muse-v1/delete-muse-accounts-email) before the job's result could be read. | | `412` | The account's weekly Muse allowance is used up. `message` says when it resets. See [GET /accounts/`email`](/docs/api-muse-v1/get-muse-accounts-email). | | `429` | Muse is limiting requests on the account right now and nothing was made. Retry in about a minute. | | `500` | An unexpected error on our side. | | `502` | Muse made a file the API cannot return (an unusual file name), or did not accept the message. A made file is in the job's chat ([GET /jobs/`jobid`?history=true](/docs/api-muse-v1/get-muse-jobs-jobid)), and running the job again uses the allowance again. Report the jobid to support. | | `503` | Nothing was made. Either the job never reached Muse (Muse could not be reached, the job could not be started, or it was interrupted before it was sent), or Muse failed to make it this time and said so, for example `the video worker glitched out before it rendered anything`. Run it again: in our tests the same prompt then worked. When Muse ends its answer with a promise such as `Back in a minute`, the job keeps waiting up to 5 more minutes for the file before it fails. | | `504` | Muse did not finish in time (a job is followed for up to 10 minutes and failed 15 minutes after it started), or the connection to Muse dropped after the job was sent. The job may still have completed in the account's Muse chat: check [GET /jobs/`jobid`?history=true](/docs/api-muse-v1/get-muse-jobs-jobid) before running it again. | | `596` | The account needs attention: Meta asks for a security check, or Muse refused to sign it in again. See [Setup Muse](/docs/start-here/setup-muse#if-the-account-needs-attention). | A sync POST whose job fails answers with the HTTP status in `error.code` and the failed job record as the body. The `replyUrl` webhook is one `POST` with `Content-Type: application/json` and this record as the body, sent once when the job completes or fails (a 10-second timeout, no retries, redirects are not followed). Use `replyRef` to match the callback to your own records. ##### Examples **Curl** ``` bash JOBID="j1012014112831908109v-u12345-email:example@gmail.com-task:3e8bc834-f243-4172-8f6e-9fc28e1a107c-bot:muse" curl -H "Authorization: Bearer …" \ "https://api.useapi.net/v1/muse/jobs/$(python3 -c "import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1],safe=''))" "$JOBID")" ``` **JavaScript** ``` javascript const token = "API token"; const jobid = "j1012014112831908109v-u12345-email:example@gmail.com-task:3e8bc834-f243-4172-8f6e-9fc28e1a107c-bot:muse"; const apiUrl = `https://api.useapi.net/v1/muse/jobs/${encodeURIComponent(jobid)}`; let job; do { await new Promise(resolve => setTimeout(resolve, 10000)); const response = await fetch(apiUrl, { headers: { "Authorization": `Bearer ${token}`, }, }); job = await response.json(); console.log(job.status); } while (job.status === "started"); console.log("job", job); ``` **Python** ``` python import requests, time, urllib.parse token = "API token" jobid = "j1012014112831908109v-u12345-email:example@gmail.com-task:3e8bc834-f243-4172-8f6e-9fc28e1a107c-bot:muse" apiUrl = "https://api.useapi.net/v1/muse/jobs/" + urllib.parse.quote(jobid, safe="") headers = { "Authorization" : f"Bearer {token}" } while True: time.sleep(10) job = requests.get(apiUrl, headers=headers).json() print(job.get("status")) if job.get("status") != "started": break print(job) ``` === URL: https://useapi.net/docs/api-muse-v1/get-muse-jobs === Document URL: https://useapi.net/docs/api-muse-v1/get-muse-jobs --- layout: default title: GET jobs description: "See what your Muse accounts are making via GET jobs in the useapi.net Muse API: the image and video jobs running now and each account's count against maxJobs." parent: Muse API v1 nav_order: 500 permalink: /docs/api-muse-v1/get-muse-jobs --- ## List running jobs October 12, 2026 --- List the jobs running right now and, for every added account, how many of its `maxJobs` slots they take. A job is listed from the moment [POST /jobs/image](/docs/api-muse-v1/post-muse-jobs-image) or [POST /jobs/video](/docs/api-muse-v1/post-muse-jobs-video) accepts it until it completes or fails. Each listed job takes one of its account's slots. When all slots of an account are taken, a new job on it is refused with `429`, and a job without `email` or inputs goes to another account that has a free slot. Uploads take no slot. Fetch a single job with [GET /jobs/`jobid`](/docs/api-muse-v1/get-muse-jobs-jobid). This endpoint reads our own records only and never calls Muse, so it is cheap to poll. > **https://api.useapi.net/v1/muse/jobs** ##### Request Headers ``` yaml Authorization: Bearer {API token} ``` - `API token` is **required**, see [Setup useapi.net](/docs/start-here/setup-useapi) for details. ##### Responses **200** **200 OK** ```json { "running": [ { "jobid": "j1012012359487240036i-u12345-email:example@gmail.com-task:bcaa91f0-24e0-4b7e-8d2c-6624bf9562e0-bot:muse", "email": "example@gmail.com", "created": "2026-10-12T01:23:59.487Z", "type": "image" } ], "accounts": { "example@gmail.com": { "running": 1, "maxJobs": 1 } } } ``` **400** **400 Bad Request** No Muse account is added yet. ```json { "error": "Please configure at least one account at https://useapi.net/docs/api-muse-v1/post-muse-accounts", "code": 400 } ``` **401** **401 Unauthorized** Invalid API token. ```json { "error": "useapi.net ⁝ Unauthorized", "code": 401 } ``` ##### Model ```typescript { // TypeScript, all fields are optional running: { jobid: string email: string // the account the job runs on created: string // ISO 8601, when the job was accepted type: 'image' | 'video' }[] accounts: { [email: string]: { running: number // jobs running on this account now maxJobs: number // its limit, set with POST /accounts } } } ``` ##### Examples **Curl** ``` bash curl "https://api.useapi.net/v1/muse/jobs" \ -H "Authorization: Bearer …" ``` **JavaScript** ``` javascript const token = "API token"; const apiUrl = "https://api.useapi.net/v1/muse/jobs"; const response = await fetch(apiUrl, { headers: { "Authorization": `Bearer ${token}`, }, }); const result = await response.json(); console.log("response", {response, result}); ``` **Python** ``` python import requests token = "API token" apiUrl = "https://api.useapi.net/v1/muse/jobs" headers = { "Authorization" : f"Bearer {token}" } response = requests.get(apiUrl, headers=headers) print(response, response.json()) ``` === URL: https://useapi.net/docs/api-muse-v1 === Document URL: https://useapi.net/docs/api-muse-v1 --- layout: default title: Muse API v1 seo_title: "Meta Muse API: AI Video and Image Generation via Your Account" description: "Meta Muse video and image API by useapi.net: 10-second clips with sound and speech from text, a photo or a video, plus images, on your own muse.ai account." nav_order: 2030 has_children: true permalink: /docs/api-muse-v1 --- # Muse API v1 October 12, 2026 useapi.net is a third-party API, not affiliated with or endorsed by Meta. The useapi.net Muse API is a REST API for the videos and images of [Muse](https://muse.ai), Meta's personal AI agent: one POST makes a 10-second video with sound and spoken dialogue from text, a photo or another video, or an image, on your own Muse account. Meta's official [Model API](https://dev.meta.ai/docs/pricing-rate-limits) sells the Muse Image model at $0.01 per image, but as of October 2026 it has no video generation and no API for the Muse agent at muse.ai, where the videos are made. This [experimental](/docs/legal) API drives the Muse agent instead. This first release covers image and video generation, and we will keep extending it to support more of what Muse can do. ## What it makes | Output | Endpoint | Details | |---|---|---| | Image | [POST /jobs/image](/docs/api-muse-v1/post-muse-jobs-image) | Text to image, or image to image from up to 9 reference images. WebP, about 2 to 2.5 megapixels. | | Video | [POST /jobs/video](/docs/api-muse-v1/post-muse-jobs-video) | Text to video, image to video or video to video, with sound and speech. MP4, about 10 seconds at 720p. | | Uploads | [POST /assets](/docs/api-muse-v1/post-muse-assets) | Your images, videos and audio files as inputs, up to 50 MB each. | | Downloads | [GET /assets/`assetId`](/docs/api-muse-v1/get-muse-assets-assetId) | Any uploaded or generated file. | ## Good to know - Aspect ratio is set in the prompt. Ask for any common ratio explicitly, for example `in exactly 4:5 aspect ratio`, and Muse follows it (we tested 1:1, 2:3, 3:4, 4:5, 9:16 and their landscape forms, and 21:9). Image to video keeps the start image's shape unless the prompt asks for another ratio. There are no model, resolution or duration parameters. - Every video is about 10 seconds long. Lines of dialogue in quotes come out as speech. - Every generated file is an [asset id](/docs/api-muse-v1/get-muse-assets-assetId) that the next job takes as an input, so edits chain without downloads or re-uploads. ## Usage limits Muse meters each account weekly. [GET /accounts/`email`](/docs/api-muse-v1/get-muse-accounts-email) returns the plan and usage in `quota`: `tier`, `percentUsed`, `resetsAt` and `quotaStatus`. | Plan | Price | Weekly allowance | |---|---|---| | Free | $0 | not published | | Power | $20/month | 500 million Muse tokens | | Maximum | $100/month | 3 billion Muse tokens | Source: Meta, [About Muse subscriptions](https://www.meta.com/help/subscriptions/1021145227643680/), October 2026. Availability can differ by region. - On the free plan, a video used 2 to 3 percent of the weekly allowance in our tests and an image 0 to 1 percent. Meta does not publish per-job token costs. - The allowance resets at `resetsAt`. Once it is used up, new jobs go to your other accounts, and a job pinned to the account answers `412`. ## Pricing A flat [$15/month](/docs/subscription) covers every useapi.net API. Each subscription adds 3 Muse accounts, and generations run on their own Muse allowance, with no per-image or per-video charge from us. ### Start using the API A single **$15/month** subscription covers every API on useapi.net — one token, all services. You connect up to 3 of your own provider accounts per service, and useapi.net handles the REST API, load balancing, and polling. [Subscribe — $15/month](https://buy.stripe.com/8x2aEX4Bd8Vh9PMg4qeUU03) [Pricing, crypto payment, and volume options](/docs/subscription) — full refund within the first 14 days if you have fewer than 50 successful generations. ## Questions ### Does Meta Muse have an API? Not for video. Meta's official [Model API](https://dev.meta.ai/docs/pricing-rate-limits) sells the Muse Image model at $0.01 per image, but as of October 2026 it has no video generation and no API for the Muse agent at muse.ai, where the videos are made. The useapi.net Muse API runs the Muse agent on your own account: add the account with its email and password, then make videos with [POST /jobs/video](/docs/api-muse-v1/post-muse-jobs-video) and images with [POST /jobs/image](/docs/api-muse-v1/post-muse-jobs-image). ### Is there a Muse video API? Not from Meta, as of October 2026. Through useapi.net, [POST /jobs/video](/docs/api-muse-v1/post-muse-jobs-video) makes an MP4 of about 10 seconds at about 720p, with sound, from a prompt, a photo or another video. Write a line in quotes and it is spoken, so a single photo becomes a talking photo. ### Is this Meta's official API? No. useapi.net is an independent, third-party API, not affiliated with or endorsed by Meta. It runs on a Muse account you own and add yourself. ### Does the free Muse plan work? Yes. In our tests on the free plan, a video used 2 to 3 percent of the weekly allowance and an image 0 to 1 percent, roughly 30 to 50 videos a week per account. See [Usage limits](#usage-limits) for the paid Muse plans. ### Where does Muse work? - Muse is available only in the US and Canada ([Meta](https://about.fb.com/news/2026/09/introducing-muse-small-business/)), so create the account there. Once it is added, you can call the API from anywhere. - Finish Muse's onboarding at [muse.ai](https://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](https://accountscenter.meta.com/password_and_security/) (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. ### How much does it cost? A flat [$15/month](/docs/subscription) to useapi.net, which covers 3 Muse accounts and every other useapi.net API. Generations use each account's own Muse allowance, with no per-image or per-video charge from us. ### Can my Muse account get banned? It can happen, and we cannot promise that it won't. Meta's [Muse terms](https://muse.ai/terms) count using the service in unauthorized, automated ways as a violation. Use a dedicated account for the API, with no other automation tools on it, as [Setup Muse](/docs/start-here/setup-muse#before-you-start) recommends. When Meta asks for a security check, the API pauses the account and emails you (see [If the account needs attention](/docs/start-here/setup-muse#if-the-account-needs-attention)). You use your account at your own risk, as our [Terms of Service](/docs/legal) say. ⚙️ [Setup Muse](/docs/start-here/setup-muse) 💻 [GitHub examples](https://github.com/useapi/muse-api) Node.js and Python: combine photos, restore old photos, make a photo talk and swap a person in a video, plus an agent skill for Claude Code and Codex 🤖 [Agent skill: muse-video](https://github.com/useapi/muse-api/tree/main/skills/muse-video) Claude Code and Codex make Muse images and videos for you: `npx skills add useapi/muse-api --skill muse-video` 📦 [Postman collection](https://www.postman.com/useapinet/useapi-net/collection/29112081-8619be70-7909-4fca-a03a-3b89e455c044) (October 12, 2026) 🤖 [LLM-friendly API spec](https://useapi.net/assets/aibot/api-muse-v1.txt) Feed this to your LLM to build integrations Articles: * [How to Generate AI Videos and Images with the Meta Muse API](/docs/articles/muse-images-videos) * [Chinese-language tutorial (中文教程): AI images and videos with dialogue](/docs/articles/muse-api-zh) Developer Community: * Discord Server * Telegram Channel === URL: https://useapi.net/docs/api-muse-v1/post-muse-accounts === Document URL: https://useapi.net/docs/api-muse-v1/post-muse-accounts --- layout: default title: POST accounts description: "Add a Muse account to the useapi.net Muse API with POST accounts: its email and password, signed in once and renewed by us, or change its maxJobs from 1 to 5." parent: Muse API v1 nav_order: 100 permalink: /docs/api-muse-v1/post-muse-accounts --- ## Add or update a Muse account October 12, 2026 --- Add a [Muse](https://muse.ai) account with its email and password. The account must have email + password sign-in: if it signs in only with an emailed code, create a password in [Meta Accounts Center](https://accountscenter.meta.com/password_and_security/) first. See [Setup Muse](/docs/start-here/setup-muse) for what the account needs and what to expect. Once added, the account is managed by the API: it signs in once, keeps the account signed in, and renews the 30-day Muse session itself, a day before it expires or whenever Muse stops accepting it, using the stored password. You never re-add the account for that. Each sign-in makes Muse email the account a login code, which needs no action. After the sign-in the API reads the account's Muse plan and weekly usage (`quota`). If Meta asks for a security check on the account, the account is saved but paused, and the answer is `596`: sign in once at [muse.ai](https://muse.ai) in a browser, complete the check, then post the same email and password again. Posting an account that is already added re-adds it. With the same password and a session Muse still accepts, the API keeps that session and signs in nothing new. `added`, `maxJobs` and the session are kept unless you send a new `maxJobs` or a new password. This endpoint does two things. To add or re-add an account, post `email` and `password`. To change only `maxJobs` on an account that is already added, post `email` and `maxJobs` without `password`: nothing is sent to Muse then. Each [useapi.net subscription](/docs/subscription) lets you add 3 Muse accounts. > **https://api.useapi.net/v1/muse/accounts** ##### Request Headers ``` yaml Authorization: Bearer {API token} Content-Type: application/json # Alternatively you can use multipart/form-data # Content-Type: multipart/form-data ``` - `API token` is **required**, see [Setup useapi.net](/docs/start-here/setup-useapi) for details. ##### Request Body Add or re-add an account: ```json { "email": "example@gmail.com", "password": "your Muse password", "maxJobs": 2 } ``` - `email` is **required**, the email of the Muse account. - `password` is **required** to add or re-add an account, the account's Muse password. Maximum length: `256` characters. A wrong password returns `400`. So does an account without a password (one that signs in only with an emailed code): create a password in [Meta Accounts Center](https://accountscenter.meta.com/password_and_security/) first. - `maxJobs` is optional, the number of jobs this account may run at the same time. Range: `1` to `5`. Default: `2` for a new account, the current value on a re-add. Update an added account: ```json { "email": "example@gmail.com", "maxJobs": 3 } ``` - `email` is **required**, the email of an added account. - `maxJobs` is **required** in this mode, the new limit. Range: `1` to `5`. ##### Responses **200** **200 OK** — the account was already added and has been re-added or updated. ```json { "email": "example@gmail.com", "maxJobs": 3, "added": "2026-10-12T00:34:19.139Z", "updated": "2026-10-12T01:13:03.745Z", "sessionExpiresAt": "2026-11-11T00:34:18.820Z", "quota": { "tier": "HATCH_FREE", "percentUsed": 0, "resetsAt": "2026-10-19T01:13:03.000Z", "quotaStatus": "SUFFICIENT", "topupBalance": 0, "readAt": "2026-10-12T01:13:03.745Z" }, "health": "OK" } ``` - `sessionExpiresAt` is when the current Muse session ends. The API renews it before then. - `quota` is the account's Muse plan and weekly usage, as read now. It is left out when Muse did not answer the read. [GET /accounts/`email`](/docs/api-muse-v1/get-muse-accounts-email) reads it live at any time. - An update (`email` + `maxJobs`) returns the same record with `quota` as last read. **201** **201 Created** — a new account was added. Same body as `200`, without `updated`. ```json { "email": "example@gmail.com", "maxJobs": 2, "added": "2026-10-12T00:34:19.139Z", "sessionExpiresAt": "2026-11-11T00:34:18.820Z", "health": "OK" } ``` **400** **400 Bad Request** — Muse refused the sign-in (a wrong email or password, or an account without a password), `password` and `maxJobs` are both missing, or a parameter is invalid. ```json { "error": "Meta account example@gmail.com has no password: it signs in with an emailed code only. Create a password in Meta Accounts Center (https://accountscenter.meta.com/password_and_security/), then add the account again", "code": 400 } ``` ```json { "error": "Parameter maxJobs (99) is more than 5", "code": 400 } ``` **401** **401 Unauthorized** Invalid API token. ```json { "error": "useapi.net ⁝ Unauthorized", "code": 401 } ``` **402** **402 Payment Required** — the subscription has expired, or a new account needs another [subscription](/docs/subscription). ```json { "error": "useapi.net ⁝ You have 1 subscription, you need 1 additional subscription (2 total) to support 4 accounts for the muse API. Upgrade at https://useapi.net/docs/subscription", "code": 402 } ``` **404** **404 Not Found** — update mode: no account is added for this `email`. ```json { "error": "Account example@gmail.com not found. Add it with email and password", "code": 404 } ``` **503** **503 Service Unavailable** — Muse could not be reached or the sign-in could not finish right now. Nothing was saved. Retry shortly. ```json { "error": "Could not reach Muse (gateway: /api/session returned no vm_id), please retry shortly", "code": 503 } ``` **596** **596 Account Error** Meta asks for a security check on the account. The account is saved with its session but paused. Sign in once at [muse.ai](https://muse.ai) in a browser, complete the check, then post the same email and password again: the API then uses the session it already has. ```json { "error": "Account example@gmail.com: Meta asks for a security check on account example@gmail.com (\"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 } ``` ##### Model `200` and `201` return the same account record. The password and the session are never returned. ```typescript { // TypeScript, all fields are optional email: string maxJobs: number // jobs this account may run at the same time added: string // ISO 8601, when the account was first added updated?: string // ISO 8601, the last re-add or maxJobs change sessionExpiresAt: string // ISO 8601, when the Muse session expires (30 days, renewed by the API) quota?: { // the account's Muse plan and weekly usage, as last read tier: string | null // Muse plan, e.g. HATCH_FREE percentUsed: number | null // share of the weekly allowance used, in whole percent resetsAt: string | null // ISO 8601, when the weekly allowance resets quotaStatus: string | null // SUFFICIENT while jobs may run topupBalance: number | null readAt: string // ISO 8601, when this was read from Muse } health: string // 'OK', or why the account needs attention (596) error?: string // errors only code?: number // errors only } ``` ##### Examples **Curl** ``` bash curl -X POST "https://api.useapi.net/v1/muse/accounts" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer …" \ -d '{ "email": "example@gmail.com", "password": "…", "maxJobs": 2 }' ``` **JavaScript** ``` javascript const token = "API token"; const apiUrl = "https://api.useapi.net/v1/muse/accounts"; const response = await fetch(apiUrl, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}`, }, body: JSON.stringify({ email: "Muse account email", password: "Muse account password", maxJobs: 2 }) }); const result = await response.json(); console.log("response", {response, result}); ``` **Python** ``` python import requests token = "API token" apiUrl = "https://api.useapi.net/v1/muse/accounts" headers = { "Content-Type": "application/json", "Authorization" : f"Bearer {token}" } data = { "email": "Muse account email", "password": "Muse account password", "maxJobs": 2 } response = requests.post(apiUrl, headers=headers, json=data) print(response, response.json()) ``` === URL: https://useapi.net/docs/api-muse-v1/post-muse-assets === Document URL: https://useapi.net/docs/api-muse-v1/post-muse-assets --- layout: default title: POST assets description: "Upload an image, video or audio file of up to 50 MB via POST assets in the useapi.net Muse API. It goes into the account's Muse workspace and returns an assetId." parent: Muse API v1 nav_order: 300 permalink: /docs/api-muse-v1/post-muse-assets --- ## Upload an asset October 12, 2026 --- Upload an image, a video or an audio file to one of your Muse accounts and get an `assetId` for it. The file is sent as the raw request body with its MIME type in the `Content-Type` header (no multipart, no JSON). The file is written into the Muse workspace of the account, where the muse.ai app keeps its files too. Nothing is stored at useapi.net. Pass the `assetId` as `image_N`, `video_N` or `audio_N` to [POST /jobs/image](/docs/api-muse-v1/post-muse-jobs-image) or [POST /jobs/video](/docs/api-muse-v1/post-muse-jobs-video). A file Muse made needs no upload: its `assetId` from the job's `result.assets[]` works the same way. The `assetId` names your API token, the account and the file, and only jobs on that account can use it. To use several uploads in one job, upload them all to the same account with `email`. The same file uploaded to the same account again gets the same `assetId` and is not written twice. An upload takes no `maxJobs` slot and uses no Muse allowance. > **https://api.useapi.net/v1/muse/assets?email=`email`** ##### Request Headers ``` yaml Authorization: Bearer {API token} Content-Type: image/jpeg # the file's MIME type, see the list below Content-Length: 316641 # most HTTP clients set it for you ``` - `API token` is **required**, see [Setup useapi.net](/docs/start-here/setup-useapi) for details. - `Content-Type` is **required**, the file's MIME type. Supported values: `image/jpeg`, `image/png`, `image/webp`, `video/mp4`, `video/quicktime`, `video/webm`, `audio/mpeg`, `audio/wav`, `audio/x-wav`, `audio/mp4`, `audio/x-m4a`, `audio/ogg`. - `Content-Length` is **required**, the file size in bytes. A request without it answers `411`. The request body is the file's raw bytes, from 1 byte to `52,428,800` bytes (50 MB). Muse takes a file in 1 MB pieces, so an upload runs at about 1 MB per second: a 50 MB file takes about a minute. ##### Query Parameters - `email` is optional, the account to upload to. When omitted, a healthy account with allowance left is picked at random. URL-encode it. ##### Responses **200** **200 OK** ```json { "assetId": "user:12345-email:6578616d706c6540676d61696c2e636f6d-image:d62d3099acbf50a46bc2155a0d18e79dc371ecdc6e4d28a072aa5bbd070bcfd3.jpg", "email": "example@gmail.com", "type": "image", "mime": "image/jpeg", "bytes": 316641 } ``` **400** **400 Bad Request** — an unsupported `Content-Type`, an empty body, or no Muse account added yet. ```json { "error": "Content-Type must be one of image/jpeg, image/png, image/webp, video/mp4, video/quicktime, video/webm, audio/mpeg, audio/wav, audio/x-wav, audio/mp4, audio/x-m4a, audio/ogg (raw file bytes in the body)", "code": 400 } ``` **401** **401 Unauthorized** Invalid API token. ```json { "error": "useapi.net ⁝ Unauthorized", "code": 401 } ``` **404** **404 Not Found** — the account named by `email` is not added. ```json { "error": "Account example@gmail.com is not configured", "code": 404 } ``` **411** **411 Length Required** — the request has no `Content-Length` header, for example a streamed (chunked) body. ```json { "error": "Content-Length is required (send the file as the raw request body)", "code": 411 } ``` **412** **412 Precondition Failed** — the account's weekly Muse allowance is used up (or every account's, without `email`). ```json { "error": "All 2 healthy accounts have used up their Muse allowance. GET /accounts/{email} re-reads it", "code": 412 } ``` **413** **413 Content Too Large** — the file is larger than 52,428,800 bytes (50 MB). ```json { "error": "File must be at most 52428800 bytes", "code": 413 } ``` **502** **502 Bad Gateway** — Muse did not store the file. Retry. ```json { "error": "Muse did not store the file (…), please retry", "code": 502 } ``` **503** **503 Service Unavailable** — Muse could not be reached, or the same file is being uploaded to the account by another request. Retry shortly. ```json { "error": "Could not connect to the Muse workspace of account example@gmail.com (…), please retry shortly", "code": 503 } ``` **596** **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](https://muse.ai) in a browser, complete the check, then add the account again with [POST /accounts](/docs/api-muse-v1/post-muse-accounts) using the same email and password. The API keeps using its session, so no new sign-in is needed. ```json { "error": "Account example@gmail.com: Meta asks for a security check on account example@gmail.com (\"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. ```json { "error": "Account example@gmail.com: Muse refused to sign in account example@gmail.com (…). Update its password with POST /accounts, or sign in at https://muse.ai to clear the block, then re-add it", "code": 596 } ``` ##### Model ```typescript { // TypeScript, all fields are optional assetId: string // user:-email:-:. email: string // the account the file was uploaded to type: 'image' | 'video' | 'audio' mime: string // the Content-Type you sent bytes: number // the file size error?: string // errors only code?: number // errors only } ``` ##### Examples **Curl** ``` bash curl -X POST "https://api.useapi.net/v1/muse/assets?email=example%40gmail.com" \ -H "Authorization: Bearer …" \ -H "Content-Type: image/jpeg" \ --data-binary @rider.jpg ``` **JavaScript** ``` javascript import fs from "fs"; const token = "API token"; const email = "Previously added account email"; const apiUrl = `https://api.useapi.net/v1/muse/assets?email=${encodeURIComponent(email)}`; const response = await fetch(apiUrl, { method: "POST", headers: { "Authorization": `Bearer ${token}`, "Content-Type": "image/jpeg", }, body: fs.readFileSync("rider.jpg") }); const result = await response.json(); console.log("response", {response, result}); ``` **Python** ``` python import requests token = "API token" email = "Previously added account email" apiUrl = "https://api.useapi.net/v1/muse/assets" headers = { "Authorization" : f"Bearer {token}", "Content-Type": "image/jpeg" } with open("rider.jpg", "rb") as f: response = requests.post(apiUrl, headers=headers, params={"email": email}, data=f.read()) print(response, response.json()) ``` === URL: https://useapi.net/docs/api-muse-v1/post-muse-jobs-image === Document URL: https://useapi.net/docs/api-muse-v1/post-muse-jobs-image --- layout: default title: POST jobs/image description: "Generate an image via POST jobs/image in the useapi.net Muse API: text to image or image to image from up to 9 inputs, aspect ratio set by prompt, WebP back." parent: Muse API v1 nav_order: 200 permalink: /docs/api-muse-v1/post-muse-jobs-image --- ## Generate an image October 12, 2026 --- Ask [Muse](https://muse.ai) 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`](/docs/api-muse-v1/get-muse-assets-assetId), or pass it as `image_N` to the next image job or to [POST /jobs/video](/docs/api-muse-v1/post-muse-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 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](/docs/api-muse-v1/post-muse-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`](/docs/api-muse-v1/get-muse-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`](/docs/api-muse-v1/get-muse-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](/docs/account-management/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](/docs/api-muse-v1/post-muse-accounts)). [GET /jobs](/docs/api-muse-v1/get-muse-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](/docs/start-here/setup-muse#usage-limits). > **https://api.useapi.net/v1/muse/jobs/image** ##### Request Headers ``` yaml Authorization: Bearer {API token} Content-Type: application/json # Alternatively you can use multipart/form-data # Content-Type: multipart/form-data ``` - `API token` is **required**, see [Setup useapi.net](/docs/start-here/setup-useapi) for details. ##### Request Body ```json { "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](/docs/api-muse-v1/post-muse-assets) or from an earlier job's `result.assets[]`. See [Inputs](#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`](/docs/api-muse-v1/get-muse-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** **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. ```json { "jobid": "j1012012322765813065i-u12345-email:example@gmail.com-task:a59c458f-efb4-4244-8ab7-f0d0c2744ea5-bot:muse", "type": "image", "email": "example@gmail.com", "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** **201 Created** — `async: true`: the job is running. Fetch the result with [GET /jobs/`jobid`](/docs/api-muse-v1/get-muse-jobs-jobid), or wait for the webhook. ```json { "jobid": "j1012012359487240036i-u12345-email:example@gmail.com-task:bcaa91f0-24e0-4b7e-8d2c-6624bf9562e0-bot:muse", "type": "image", "email": "example@gmail.com", "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** **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`](/docs/api-muse-v1/get-muse-jobs-jobid). **400** **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. ```json { "error": "The prompt names image_2, but 'image_2' was not provided in the request body", "code": 400 } ``` ```json { "error": "Parameter model not supported", "code": 400 } ``` ```json { "error": "Please configure at least one account at https://useapi.net/docs/api-muse-v1/post-muse-accounts", "code": 400 } ``` **401** **401 Unauthorized** Invalid API token. ```json { "error": "useapi.net ⁝ Unauthorized", "code": 401 } ``` **403** **403 Forbidden** — an input asset id belongs to another API token: ```json { "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](/docs/api-muse-v1/get-muse-jobs-jobid#error-codes). **404** **404 Not Found** — the account named by `email`, or the account an input lives on, is not added. ```json { "error": "Account example@gmail.com is not configured", "code": 404 } ``` **412** **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. ```json { "error": "All 2 healthy accounts have used up their Muse allowance. GET /accounts/{email} re-reads it", "code": 412 } ``` **429** **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. ```json { "error": "All 1 accounts are running their maximum number of jobs (maxJobs), please retry shortly", "code": 429 } ``` **502** **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](/docs/api-muse-v1/get-muse-jobs-jobid). Running the job again uses the allowance again. **503** **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** **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](/docs/api-muse-v1/get-muse-jobs-jobid) before running it again. **596** **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](https://muse.ai) in a browser, complete the check, then add the account again with [POST /accounts](/docs/api-muse-v1/post-muse-accounts) using the same email and password. The API keeps using its session, so no new sign-in is needed. ```json { "error": "Account example@gmail.com: Meta asks for a security check on account example@gmail.com (\"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. ```json { "error": "Account example@gmail.com: Muse refused to sign in account example@gmail.com (…). 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: ```json { "error": "No healthy Muse account configured. Check GET /accounts", "code": 596 } ``` ##### Model The job record, `type: "image"`. [GET /jobs/`jobid`](/docs/api-muse-v1/get-muse-jobs-jobid) and the `replyUrl` webhook return the same shape. Errors before a job is created return `{ error, code }`. ```typescript { // TypeScript, all fields are optional jobid: string // j-u-email:-task:-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 // 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** ``` bash 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" }' ``` **JavaScript** ``` javascript 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); ``` **Python** ``` python 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) ``` === URL: https://useapi.net/docs/api-muse-v1/post-muse-jobs-video === Document URL: https://useapi.net/docs/api-muse-v1/post-muse-jobs-video --- layout: default title: POST jobs/video seo_title: "Meta Muse Video API: POST jobs/video, 10-Second Clips with Sound" description: "Generate a 10-second Meta Muse video with sound via POST jobs/video in the useapi.net Muse API: text, image or video to video, talking photos, up to 15 inputs." parent: Muse API v1 nav_order: 210 permalink: /docs/api-muse-v1/post-muse-jobs-video --- ## Generate a video October 12, 2026 --- Ask [Muse](https://muse.ai) 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`](/docs/api-muse-v1/get-muse-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 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](/docs/api-muse-v1/post-muse-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`](/docs/api-muse-v1/get-muse-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`](/docs/api-muse-v1/get-muse-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](/docs/account-management/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](/docs/api-muse-v1/post-muse-accounts)). [GET /jobs](/docs/api-muse-v1/get-muse-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](/docs/start-here/setup-muse#usage-limits). > **https://api.useapi.net/v1/muse/jobs/video** ##### Request Headers ``` yaml Authorization: Bearer {API token} Content-Type: application/json # Alternatively you can use multipart/form-data # Content-Type: multipart/form-data ``` - `API token` is **required**, see [Setup useapi.net](/docs/start-here/setup-useapi) for details. ##### Request Body ```json { "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](/docs/api-muse-v1/post-muse-assets) or from an earlier job's `result.assets[]`. See [Inputs](#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`](/docs/api-muse-v1/get-muse-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** **200 OK** — sync mode, the video is ready. This one animated an uploaded image, 83 seconds after the request. ```json { "jobid": "j1012012156286615380v-u12345-email:example@gmail.com-task:20842037-a004-4dba-b640-225a8197385b-bot:muse", "type": "video", "email": "example@gmail.com", "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** **201 Created** — `async: true`: the job is running. Fetch the result with [GET /jobs/`jobid`](/docs/api-muse-v1/get-muse-jobs-jobid), or wait for the webhook. ```json { "jobid": "j1012014112831908109v-u12345-email:example@gmail.com-task:3e8bc834-f243-4172-8f6e-9fc28e1a107c-bot:muse", "type": "video", "email": "example@gmail.com", "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** **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`](/docs/api-muse-v1/get-muse-jobs-jobid). **400** **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. ```json { "error": "The prompt names image_2, but 'image_2' was not provided in the request body", "code": 400 } ``` ```json { "error": "Parameter image_1 must be a image asset, this one is a video", "code": 400 } ``` ```json { "error": "Please configure at least one account at https://useapi.net/docs/api-muse-v1/post-muse-accounts", "code": 400 } ``` **401** **401 Unauthorized** Invalid API token. ```json { "error": "useapi.net ⁝ Unauthorized", "code": 401 } ``` **403** **403 Forbidden** — an input asset id belongs to another API token: ```json { "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](/docs/api-muse-v1/get-muse-jobs-jobid#error-codes). **404** **404 Not Found** — the account named by `email`, or the account an input lives on, is not added. ```json { "error": "Account example@gmail.com is not configured", "code": 404 } ``` **412** **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. ```json { "error": "All 2 healthy accounts have used up their Muse allowance. GET /accounts/{email} re-reads it", "code": 412 } ``` **429** **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. ```json { "error": "All 1 accounts are running their maximum number of jobs (maxJobs), please retry shortly", "code": 429 } ``` **502** **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](/docs/api-muse-v1/get-muse-jobs-jobid). Running the job again uses the allowance again. **503** **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** **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](/docs/api-muse-v1/get-muse-jobs-jobid) before running it again. **596** **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](https://muse.ai) in a browser, complete the check, then add the account again with [POST /accounts](/docs/api-muse-v1/post-muse-accounts) using the same email and password. The API keeps using its session, so no new sign-in is needed. ```json { "error": "Account example@gmail.com: Meta asks for a security check on account example@gmail.com (\"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. ```json { "error": "Account example@gmail.com: Muse refused to sign in account example@gmail.com (…). 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: ```json { "error": "No healthy Muse account configured. Check GET /accounts", "code": 596 } ``` ##### Model The job record, `type: "video"`. [GET /jobs/`jobid`](/docs/api-muse-v1/get-muse-jobs-jobid) and the `replyUrl` webhook return the same shape. Errors before a job is created return `{ error, code }`. ```typescript { // TypeScript, all fields are optional jobid: string // j-u-email:-task:-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 // 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** ``` bash 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 }' ``` **JavaScript** ``` javascript 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); ``` **Python** ``` python 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) ``` === Cross-reference: General Q&A === For cross-cutting tips (rate limiting, prompt moderation, common patterns) see https://useapi.net/assets/aibot/qa.txt WARNING: Q&A items are general-purpose and may reference deprecated APIs, sunsetted services, or scenarios not applicable to the API documented above. Cross-check against this service's endpoint documentation before applying any Q&A guidance.