Reference
Endpoints
All 20 endpoints under https://api.musavir.ai/v1, generated from the server’s own route table — summaries, scopes and field lists are the ones the API validates against, not a description of them.
Conventions
- Base URL.
https://api.musavir.ai/v1. The version is in the path and only ever changes additively — new fields and new endpoints may appear inside/v1; existing meanings do not change. - Authentication.
Authorization: Bearer mk_live_…on every request. - Envelopes. One resource under
data; a collection underdatapluspagination; a failure undererror. - Bodies.
application/json, up to 256 KB by default. Unknown fields are refused at every depth with400 unknown_field. - Timestamps are ISO-8601 UTC strings. Credit amounts are decimal strings with six decimal places — never JSON numbers.
- Ids and cursors are opaque. Store them, resend them verbatim, never parse or construct one.
- Trailing slashes are normalized:
/v1/jobs/reaches the same route as/v1/jobs.
curl "https://api.musavir.ai/v1/jobs?limit=25&status=succeeded" \
-H "Authorization: Bearer $MUSAVIR_API_KEY"
# → { "data": [ … ], "pagination": { "hasMore": true, "nextCursor": "eyJ…" } }
# Next page: append &cursor=eyJ…Headers
| Header | Direction | When |
|---|---|---|
| Authorization | Request | Always. Bearer mk_live_… |
| Content-Type | Request | Required on every request with a body: application/json. |
| Idempotency-Key | Request | Required on endpoints that spend credits; accepted and ignored elsewhere. 1–128 printable ASCII characters. See idempotency. |
| X-Request-Id | Response | Always. Log it — it is what support looks a request up by. |
| Location | Response | On 201 and 202: the resource that was created or accepted. |
| Retry-After | Response | On 202 (when to poll first), and on a 409 idempotency_in_progress or a 429. |
| Idempotent-Replayed | Response | true when a request replayed an earlier one instead of doing new work. |
| Allow | Response | On 405: the methods that path does accept. |
| X-RateLimit-Limit / -Remaining / -Reset | Response | Only when rate limiting is enforced, which it is not today. Absent rather than fictional — read them if present, do not depend on them. |
Job statuses
A job’s status is one of 5 values, and the set is frozen inside /v1 — new values may be added, existing ones never change meaning:
queued— accepted, not started. Keep polling.running— being rendered or securing its finished output. Keep polling; do not impose a short client-side generation timeout. The job is durable, so you can resume later with its id.action_required— waiting on you.requiredActioncarries the previews and the exact call that continues it. Polling alone will never advance it.succeeded— terminal.resultholds freshly signed URLs and their expiry.failed— terminal.failure.codeis for branching,failure.messageis for showing.
Scopes
A key carries one or more scopes and can do nothing else. Grant the least an integration needs: these credentials act as the account, so a service that only reads jobs must not also be able to spend the balance. A call to an endpoint outside the key’s scopes is 403 missing_scope, naming the scope it wanted.
Start image generations. Spends credits.
Unlocks
Start video generations, and approve the character sheets a video pauses on. Spends credits.
Unlocks
Presign reference images, clips and audio, and normalize an uploaded clip. Puts objects in the account’s own storage namespace; spends nothing.
Unlocks
List characters and read one, including a freshly signed avatar URL.
Unlocks
Create character drafts, presign their selfies, and start training. Training spends credits.
Unlocks
Read and list the jobs this account started through the API.
Unlocks
Read the credit balance, API usage, rate-limit budgets and model capabilities. The safest scope to grant: it cannot start work or spend anything.
Unlocks
Generation and jobs
Start a render, then read the job it produced. These are the endpoints that spend credits.
| Endpoint | Scope | What it does |
|---|---|---|
| POST /v1/jobs/:id/actions/confirm-inputs | jobs:read | Confirm the reviewed current inputs of an older paused job. |
| POST /v1/images | images:generate | Start an image generation and return the job to poll. |
| POST /v1/videos | videos:generate | Start a video generation and return the job to poll. |
| GET /v1/jobs/:id | jobs:read | Fetch one job created through the API. |
| GET /v1/jobs | jobs:read | List jobs created through the API, newest first. |
| POST /v1/jobs/:id/actions/approve | videos:generate | Approve the character sheets a video job is waiting on. |
Confirm the reviewed current inputs of an older paused job.
Requires jobs:read and the matching images:generate or videos:generate scope. Read requiredAction, show its ordered previews and pricing, obtain user approval, then echo inputFingerprint. A stale review returns 409. Reuses the existing job and reservation; retrying the same accepted fingerprint does not create or fund another job.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
| inputFingerprint | string | Required | matches ^[a-f0-9]{64}$ |
Start an image generation and return the job to poll.
Only prompt is required — the server chooses a resolution and aspect ratio when they are omitted. Responds 202 with a job; poll GET /v1/jobs/:id (or follow Location) until it reaches succeeded or failed. Requires an Idempotency-Key, so a retried request returns the original job instead of rendering and charging twice. For media created for a Layers design, pass its designId; edit access is required and the admin-configured Layers multiplier applies (default 3.5×) instead of the API multiplier.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
| prompt | string | Required | at most 32000 characters |
| characterIds | uuid[] | Optional | limit comes from images.maxCast in GET /v1/capabilities |
| templateId | uuid | Optional | — |
| designId | uuid | Optional | — |
| refKeys | string[] | Optional | limit comes from images.references.byCastSize in GET /v1/capabilities |
| resolution | enum | Optional | accepted values come from images.resolutions in GET /v1/capabilities |
| ratio | enum | Optional | accepted values come from GET /v1/capabilities |
Start a video generation and return the job to poll.
As POST /v1/images, plus duration, audio, and video/audio references. Accepted durations, resolutions and reference limits depend on the configured model — read GET /v1/capabilities rather than hardcoding them. A video whose cast needs character sheets pauses at action_required; approve it to continue.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
| prompt | string | Required | at most 32000 characters |
| characterIds | uuid[] | Optional | limit comes from videos.maxCast in GET /v1/capabilities |
| templateId | uuid | Optional | — |
| designId | uuid | Optional | — |
| refKeys | string[] | Optional | limit comes from videos.references.byCastSize in GET /v1/capabilities |
| resolution | enum | Optional | accepted values come from videos.resolutions in GET /v1/capabilities |
| ratio | enum | Optional | accepted values come from GET /v1/capabilities |
| durationSeconds | integer | Optional | limit comes from videos.durationSeconds in GET /v1/capabilities |
| videoRefKeys | string[] | Optional | limit comes from videos.references.maxVideoReferences in GET /v1/capabilities |
| audioRefKeys | string[] | Optional | limit comes from videos.references.maxAudioReferences in GET /v1/capabilities |
| audio | boolean | Optional | — |
Fetch one job created through the API.
Result URLs are signed for this response only and carry result.expiresAt — sign them again by re-reading the job rather than storing them. Jobs created in the Musavir studio are not readable with an API key and answer 404. A running job may still be rendering or may be securing its finished output; keep the job id and continue polling until a terminal status instead of imposing a short client-side timeout.
List jobs created through the API, newest first.
Every key on the account sees the same account-wide list, filtered to API-origin jobs. Pass pagination.nextCursor back as cursor for the next page; a cursor is opaque and must not be parsed or constructed.
Query parameters
| Field | Type | Required | Notes |
|---|---|---|---|
| cursor | string | Optional | at most 200 characters |
| limit | integer | Optional | 1–100 |
| status | enum | Optional | one of: queued, running, action_required, succeeded, failed |
| kind | enum | Optional | one of: image, video |
| createdAfter | string | Optional | ISO-8601 date-time with offset |
| createdBefore | string | Optional | ISO-8601 date-time with offset |
Approve the character sheets a video job is waiting on.
Only meaningful while the job reports action_required; the sheets to review are in requiredAction.previews. Approving an already approved job returns that job, so a retry after a lost response is safe.
Reference uploads
Presigned, direct-to-storage targets for the media a generation can reference.
| Endpoint | Scope | What it does |
|---|---|---|
| POST /v1/uploads/images | uploads:write | Presign reference-image uploads for an image or video generation. |
| POST /v1/uploads/videos | uploads:write | Presign MP4/MOV reference-clip uploads for a video generation. |
| POST /v1/uploads/audio | uploads:write | Presign MP3/WAV reference-audio uploads for a video generation. |
| POST /v1/uploads/videos/normalize | uploads:write | Normalize a reference clip, retaining the original metadata response contract. |
| POST /v1/uploads/videos/prepare | uploads:write | Queue reference-video preparation and immediately return a durable receipt. |
| GET /v1/uploads/preparations/:id | uploads:write | Read an owned upload preparation receipt. |
Presign reference-image uploads for an image or video generation.
PUT the file bytes directly to uploadUrl with exactly the headers in headers. The bytes never pass through this API. sizeBytes is REQUIRED and is signed into the URL: the upload must be exactly that many bytes or storage rejects it, so a URL cannot be reused for a larger file than it was issued for. maxBytes is rechecked from the stored object when the reference is used as well. The returned uploadKey is opaque. Store it and pass it back verbatim — never parse it, derive one from a filename, or construct one by hand; its layout is server-owned and can change without a version bump, and a key outside your own namespace is rejected. How many of these keys one generation may carry depends on its cast — each character reserves provider slots — so read /v1/capabilities for the budget rather than assuming this endpoint’s ceiling.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
| files | object[] | Required | limit comes from images.references.providerMaxImages in GET /v1/capabilities |
| files[].contentType | string | Required | accepted values come from images.references.imageContentTypes in GET /v1/capabilities at most 128 characters |
| files[].sizeBytes | integer | Required | limit comes from images.references.maxImageBytes in GET /v1/capabilities |
| files[].filename | string | Optional | at most 255 characters |
Presign MP4/MOV reference-clip uploads for a video generation.
PUT the file bytes directly to uploadUrl with exactly the headers in headers. The bytes never pass through this API. sizeBytes is REQUIRED and is signed into the URL: the upload must be exactly that many bytes or storage rejects it, so a URL cannot be reused for a larger file than it was issued for. maxBytes is rechecked from the stored object when the reference is used as well. The returned uploadKey is opaque. Store it and pass it back verbatim — never parse it, derive one from a filename, or construct one by hand; its layout is server-owned and can change without a version bump, and a key outside your own namespace is rejected. A reference clip must then be normalized through POST /v1/uploads/videos/normalize, which is the only place its duration and dimensions are actually checked. How many clips one generation accepts is model-dependent — see videos.references.maxVideoReferences in /v1/capabilities.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
| files | object[] | Required | limit comes from videos.references.maxVideoReferences in GET /v1/capabilities |
| files[].contentType | string | Required | accepted values come from videos.references.videoContentTypes in GET /v1/capabilities at most 128 characters |
| files[].sizeBytes | integer | Required | ceiling is the maxBytes returned beside each upload target |
| files[].filename | string | Optional | at most 255 characters |
Presign MP3/WAV reference-audio uploads for a video generation.
PUT the file bytes directly to uploadUrl with exactly the headers in headers. The bytes never pass through this API. sizeBytes is REQUIRED and is signed into the URL: the upload must be exactly that many bytes or storage rejects it, so a URL cannot be reused for a larger file than it was issued for. maxBytes is rechecked from the stored object when the reference is used as well. The returned uploadKey is opaque. Store it and pass it back verbatim — never parse it, derive one from a filename, or construct one by hand; its layout is server-owned and can change without a version bump, and a key outside your own namespace is rejected. How many audio references one generation accepts is model-dependent — see videos.references.maxAudioReferences in /v1/capabilities.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
| files | object[] | Required | limit comes from videos.references.maxAudioReferences in GET /v1/capabilities |
| files[].contentType | string | Required | accepted values come from videos.references.audioContentTypes in GET /v1/capabilities at most 128 characters |
| files[].sizeBytes | integer | Required | ceiling is the maxBytes returned beside each upload target |
| files[].filename | string | Optional | at most 255 characters |
Normalize a reference clip, retaining the original metadata response contract.
After the PUT, queue durable preparation and wait at most 25 seconds. Success returns the original video_reference fields: authoritative uploadKey, durationSeconds, width, height, normalized. Always use that returned uploadKey. If preparation is still pending, HTTP 503 provider_unavailable and Retry-After include the original uploadKey and, once retained, preparationId. Retry with the SAME original uploadKey and allowTrim to recover the existing preparation; no decoder work runs inside this request and no credits are spent. For immediate 202 receipts use POST /v1/uploads/videos/prepare instead. An overlong clip returns VIDEO_TRIM_CONSENT_REQUIRED with measured durationSeconds/maxDurationSeconds. Obtain permission, then retry the original uploadKey with allowTrim: true. Raw uploads remain until the saved prepared result has passed its cleanup grace.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
| uploadKey | string | Required | at most 512 characters |
| allowTrim | boolean | Optional | — |
Queue reference-video preparation and immediately return a durable receipt.
Returns 202 and Location while queued or running, or 200 for a terminal replay. Poll GET /v1/uploads/preparations/:id until succeeded, then use result.key as the authoritative uploadKey. Retrying the same original uploadKey and allowTrim reuses the existing receipt. A failed receipt carries safe errorCode, errorMessage and optional measured trim errorDetails. No decoder work runs in this HTTP request and no credits are spent.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
| uploadKey | string | Required | at most 512 characters |
| allowTrim | boolean | Optional | — |
Read an owned upload preparation receipt.
Poll until status is succeeded or failed. Succeeded result.key is the authoritative prepared upload key; failure contains a safe errorCode, errorMessage and optional trim consent errorDetails. This read does not decode media or buy a generation.
Characters
The draft → upload → train lifecycle, and reading the result.
| Endpoint | Scope | What it does |
|---|---|---|
| POST /v1/characters | characters:write | Create a character as a draft, before it has any photos. |
| POST /v1/characters/:id/uploads | characters:write | Presign selfie uploads into this character’s own namespace. |
| POST /v1/characters/:id/actions/train | characters:write | Attach the uploaded selfies and start training. Returns 202. |
| GET /v1/characters | characters:read | The account’s characters, newest first, cursor-paginated. |
| GET /v1/characters/:id | characters:read | One character: status, progress, and a freshly signed avatar URL. |
Create a character as a draft, before it has any photos.
Costs nothing and starts nothing. Keep the returned id: the draft is where selfies are uploaded and it is NOT returned by GET /v1/characters, which lists characters that have begun training — an abandoned draft must not sit in a library looking like a broken character. GET /v1/characters/{id} returns it throughout.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
| name | string | Required | at most 120 characters |
Presign selfie uploads into this character’s own namespace.
PUT each file directly to its uploadUrl with exactly the headers in headers, then pass the returned uploadKey values to the train action. These keys are NOT interchangeable with generation reference keys — each namespace is refused by the other — and the accepted content types differ: selfies take the HEIC/HEIF a phone camera produces and refuse GIF. Read characters.selfieContentTypes from /v1/capabilities rather than hardcoding either list.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
| files | object[] | Required | limit comes from characters.maxSelfies in GET /v1/capabilities |
| files[].contentType | string | Required | accepted values come from characters.selfieContentTypes in GET /v1/capabilities at most 128 characters |
| files[].sizeBytes | integer | Required | ceiling is the maxBytes returned beside each upload target |
| files[].filename | string | Optional | at most 255 characters |
Attach the uploaded selfies and start training. Returns 202.
Requires an Idempotency-Key: this is the call that spends credits, and a retry must return the same character rather than train it twice. Only keys returned by this character’s own upload endpoint are accepted. Poll GET /v1/characters/{id} until status is ready or failed. Stop automatic training retries for awaiting_confirmation (review the ordered photos in Studio) or reconciliation_pending (the saved avatar is available; billing needs reconciliation). A 402 here means the balance could not cover training.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
| selfieKeys | string[] | Required | limit comes from characters.maxSelfies in GET /v1/capabilities |
The account’s characters, newest first, cursor-paginated.
Characters created in the studio are listed alongside those created through the API: a character is an account resource, and using one an integration did not create is the point of the scope. Drafts that have not started training are excluded. Follow pagination.nextCursor rather than assuming one page holds everything.
Query parameters
| Field | Type | Required | Notes |
|---|---|---|---|
| limit | integer | Optional | 1–100 |
| cursor | string | Optional | at most 512 characters |
One character: status, progress, and a freshly signed avatar URL.
The avatar URL is signed for this response only and dies at avatar.expiresAt — never store it, re-read the character instead. A character belonging to another account is a 404, the same answer as one that does not exist.
Account and capabilities
What you are allowed to do, what the models accept, and what you have already spent.
| Endpoint | Scope | What it does |
|---|---|---|
| GET /v1/account/usage | usage:read | Credit balance, plan, auto-reload settings, and API usage for the current billing period. |
| GET /v1/account/limits | usage:read | The calling key's rate-limit budgets, when limiting is enabled. |
| GET /v1/capabilities | usage:read | Durations, resolutions, ratios, media types and reference limits the models accept. |
Credit balance, plan, auto-reload settings, and API usage for the current billing period.
Credit amounts are decimal strings with six decimal places. Usage is reported both account-wide and for the calling key, over the period named in period. The autoReload block explains why a balance may refill on its own: whether automatic top-ups are on, the balance that triggers one, how much each adds, the monthly ceiling, how much has been added so far this UTC calendar month, and why the last attempt stopped. It is READ-ONLY here, and there is no endpoint in /v1 that changes it — an API key may never alter a payment setting. Auto-reload is a standing permission to charge a saved card with nobody present, so switching it on or changing what it charges is a signed-in action in the Musavir studio, taken by the person who owns the card rather than by a key that could be copied out of a config file.
The calling key's rate-limit budgets, when limiting is enabled.
Reading this endpoint does not consume any budget. The generation budget is listed even though this call is not a generation, so a client can plan a batch before it starts.
Durations, resolutions, ratios, media types and reference limits the models accept.
The runtime source of truth. Values here are read from the live server configuration on every request, so they follow a model change without a documentation or SDK release. Never hardcode a duration or resolution against the documented examples instead of this.