Skip to content
MV2API docsStudioDownload for agentsGet an API key

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 under data plus pagination; a failure under error.
  • Bodies. application/json, up to 256 KB by default. Unknown fields are refused at every depth with 400 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.
Paging a list
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

Request and response headers used by the API
HeaderDirectionWhen
AuthorizationRequestAlways. Bearer mk_live_…
Content-TypeRequestRequired on every request with a body: application/json.
Idempotency-KeyRequestRequired on endpoints that spend credits; accepted and ignored elsewhere. 1–128 printable ASCII characters. See idempotency.
X-Request-IdResponseAlways. Log it — it is what support looks a request up by.
LocationResponseOn 201 and 202: the resource that was created or accepted.
Retry-AfterResponseOn 202 (when to poll first), and on a 409 idempotency_in_progress or a 429.
Idempotent-ReplayedResponsetrue when a request replayed an earlier one instead of doing new work.
AllowResponseOn 405: the methods that path does accept.
X-RateLimit-Limit / -Remaining / -ResetResponseOnly 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. requiredAction carries the previews and the exact call that continues it. Polling alone will never advance it.
  • succeeded — terminal. result holds freshly signed URLs and their expiry.
  • failed — terminal. failure.code is for branching, failure.message is 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.

images:generate

Start image generations. Spends credits.

Unlocks

videos:generate

Start video generations, and approve the character sheets a video pauses on. Spends credits.

Unlocks

uploads:write

Presign reference images, clips and audio, and normalize an uploaded clip. Puts objects in the account’s own storage namespace; spends nothing.

Unlocks

characters:read

List characters and read one, including a freshly signed avatar URL.

Unlocks

characters:write

Create character drafts, presign their selfies, and start training. Training spends credits.

Unlocks

jobs:read

Read and list the jobs this account started through the API.

Unlocks

usage:read

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.

Generation and jobs endpoints
EndpointScopeWhat it does
POST /v1/jobs/:id/actions/confirm-inputsjobs:readConfirm the reviewed current inputs of an older paused job.
POST /v1/imagesimages:generateStart an image generation and return the job to poll.
POST /v1/videosvideos:generateStart a video generation and return the job to poll.
GET /v1/jobs/:idjobs:readFetch one job created through the API.
GET /v1/jobsjobs:readList jobs created through the API, newest first.
POST /v1/jobs/:id/actions/approvevideos:generateApprove the character sheets a video job is waiting on.
POST/v1/jobs/:id/actions/confirm-inputs

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.

jobs:readgeneration class

Request body

Request body for POST /v1/jobs/:id/actions/confirm-inputs
FieldTypeRequiredNotes
inputFingerprintstringRequired
matches ^[a-f0-9]{64}$
POST/v1/images

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.

images:generategeneration classIdempotency-Key required

Request body

Request body for POST /v1/images
FieldTypeRequiredNotes
promptstringRequired
at most 32000 characters
characterIdsuuid[]Optional
limit comes from images.maxCast in GET /v1/capabilities
templateIduuidOptional—
designIduuidOptional—
refKeysstring[]Optional
limit comes from images.references.byCastSize in GET /v1/capabilities
resolutionenumOptional
accepted values come from images.resolutions in GET /v1/capabilities
ratioenumOptional
accepted values come from GET /v1/capabilities
POST/v1/videos

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.

videos:generategeneration classIdempotency-Key required

Request body

Request body for POST /v1/videos
FieldTypeRequiredNotes
promptstringRequired
at most 32000 characters
characterIdsuuid[]Optional
limit comes from videos.maxCast in GET /v1/capabilities
templateIduuidOptional—
designIduuidOptional—
refKeysstring[]Optional
limit comes from videos.references.byCastSize in GET /v1/capabilities
resolutionenumOptional
accepted values come from videos.resolutions in GET /v1/capabilities
ratioenumOptional
accepted values come from GET /v1/capabilities
durationSecondsintegerOptional
limit comes from videos.durationSeconds in GET /v1/capabilities
videoRefKeysstring[]Optional
limit comes from videos.references.maxVideoReferences in GET /v1/capabilities
audioRefKeysstring[]Optional
limit comes from videos.references.maxAudioReferences in GET /v1/capabilities
audiobooleanOptional—
GET/v1/jobs/:id

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.

jobs:readread class
GET/v1/jobs

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.

jobs:readread class

Query parameters

Query parameters for GET /v1/jobs
FieldTypeRequiredNotes
cursorstringOptional
at most 200 characters
limitintegerOptional
1–100
statusenumOptional
one of: queued, running, action_required, succeeded, failed
kindenumOptional
one of: image, video
createdAfterstringOptional
ISO-8601 date-time with offset
createdBeforestringOptional
ISO-8601 date-time with offset
POST/v1/jobs/:id/actions/approve

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.

videos:generategeneration class

Reference uploads

Presigned, direct-to-storage targets for the media a generation can reference.

Reference uploads endpoints
EndpointScopeWhat it does
POST /v1/uploads/imagesuploads:writePresign reference-image uploads for an image or video generation.
POST /v1/uploads/videosuploads:writePresign MP4/MOV reference-clip uploads for a video generation.
POST /v1/uploads/audiouploads:writePresign MP3/WAV reference-audio uploads for a video generation.
POST /v1/uploads/videos/normalizeuploads:writeNormalize a reference clip, retaining the original metadata response contract.
POST /v1/uploads/videos/prepareuploads:writeQueue reference-video preparation and immediately return a durable receipt.
GET /v1/uploads/preparations/:iduploads:writeRead an owned upload preparation receipt.
POST/v1/uploads/images

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.

uploads:writeupload class

Request body

Request body for POST /v1/uploads/images
FieldTypeRequiredNotes
filesobject[]Required
limit comes from images.references.providerMaxImages in GET /v1/capabilities
files[].contentTypestringRequired
accepted values come from images.references.imageContentTypes in GET /v1/capabilities
at most 128 characters
files[].sizeBytesintegerRequired
limit comes from images.references.maxImageBytes in GET /v1/capabilities
files[].filenamestringOptional
at most 255 characters
POST/v1/uploads/videos

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.

uploads:writeupload class

Request body

Request body for POST /v1/uploads/videos
FieldTypeRequiredNotes
filesobject[]Required
limit comes from videos.references.maxVideoReferences in GET /v1/capabilities
files[].contentTypestringRequired
accepted values come from videos.references.videoContentTypes in GET /v1/capabilities
at most 128 characters
files[].sizeBytesintegerRequired
ceiling is the maxBytes returned beside each upload target
files[].filenamestringOptional
at most 255 characters
POST/v1/uploads/audio

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.

uploads:writeupload class

Request body

Request body for POST /v1/uploads/audio
FieldTypeRequiredNotes
filesobject[]Required
limit comes from videos.references.maxAudioReferences in GET /v1/capabilities
files[].contentTypestringRequired
accepted values come from videos.references.audioContentTypes in GET /v1/capabilities
at most 128 characters
files[].sizeBytesintegerRequired
ceiling is the maxBytes returned beside each upload target
files[].filenamestringOptional
at most 255 characters
POST/v1/uploads/videos/normalize

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.

uploads:writeupload class

Request body

Request body for POST /v1/uploads/videos/normalize
FieldTypeRequiredNotes
uploadKeystringRequired
at most 512 characters
allowTrimbooleanOptional—
POST/v1/uploads/videos/prepare

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.

uploads:writeupload class

Request body

Request body for POST /v1/uploads/videos/prepare
FieldTypeRequiredNotes
uploadKeystringRequired
at most 512 characters
allowTrimbooleanOptional—
GET/v1/uploads/preparations/:id

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.

Characters endpoints
EndpointScopeWhat it does
POST /v1/characterscharacters:writeCreate a character as a draft, before it has any photos.
POST /v1/characters/:id/uploadscharacters:writePresign selfie uploads into this character’s own namespace.
POST /v1/characters/:id/actions/traincharacters:writeAttach the uploaded selfies and start training. Returns 202.
GET /v1/characterscharacters:readThe account’s characters, newest first, cursor-paginated.
GET /v1/characters/:idcharacters:readOne character: status, progress, and a freshly signed avatar URL.
POST/v1/characters

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.

characters:writecharacter class

Request body

Request body for POST /v1/characters
FieldTypeRequiredNotes
namestringRequired
at most 120 characters
POST/v1/characters/:id/uploads

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

Request body for POST /v1/characters/:id/uploads
FieldTypeRequiredNotes
filesobject[]Required
limit comes from characters.maxSelfies in GET /v1/capabilities
files[].contentTypestringRequired
accepted values come from characters.selfieContentTypes in GET /v1/capabilities
at most 128 characters
files[].sizeBytesintegerRequired
ceiling is the maxBytes returned beside each upload target
files[].filenamestringOptional
at most 255 characters
POST/v1/characters/:id/actions/train

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.

characters:writecharacter classIdempotency-Key required

Request body

Request body for POST /v1/characters/:id/actions/train
FieldTypeRequiredNotes
selfieKeysstring[]Required
limit comes from characters.maxSelfies in GET /v1/capabilities
GET/v1/characters

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

Query parameters for GET /v1/characters
FieldTypeRequiredNotes
limitintegerOptional
1–100
cursorstringOptional
at most 512 characters
GET/v1/characters/:id

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.

Account and capabilities endpoints
EndpointScopeWhat it does
GET /v1/account/usageusage:readCredit balance, plan, auto-reload settings, and API usage for the current billing period.
GET /v1/account/limitsusage:readThe calling key's rate-limit budgets, when limiting is enabled.
GET /v1/capabilitiesusage:readDurations, resolutions, ratios, media types and reference limits the models accept.
GET/v1/account/usage

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.

usage:readread class
GET/v1/account/limits

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.

usage:readread class
GET/v1/capabilities

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.

usage:readread class