Skip to content
MV2API docsStudioDownload for agentsGet an API key

Quickstart

Key to finished image, in four calls.

Everything below is copy-pasteable curl. It needs a shell, curl, and jq for the two samples that read a field out of a response.

Create a key

Open the developer dashboard and create a key. For this walkthrough it needs two scopes: images:generate to start the render and jobs:read to poll it. Add usage:read as well if you want step 2 to work — it is the scope that reads capabilities, and it can neither start work nor spend anything.

The API is part of Musavir Pro: without an active subscription, step 3 answers 403 subscription_required — a trial counts, and steps 1 and 2 work on any plan, since reading your usage, limits and capabilities is never gated.

The key is shown once. Put it in your environment rather than in the samples, so it never lands in a shell history or a screenshot:

Export the key
export MUSAVIR_API_KEY="mk_live_your_key_here"

Ask what the models accept

Optional for the first render, essential for real code. This is the runtime source of truth for durations, resolutions, aspect-ratio rules, accepted content types, and how many references a request may carry with a given cast. Read it at startup and cache it briefly; do not copy its numbers into your source.

Read the live capabilities
curl https://api.musavir.ai/v1/capabilities \
  -H "Authorization: Bearer $MUSAVIR_API_KEY"

It also reports whether video is switched on at all (videos.available), which is cheaper to check than to discover as a 503.

Start an image

Only prompt is required. Send nothing else and the server chooses a resolution and an aspect ratio for you.

Start an image generation
curl -i -X POST https://api.musavir.ai/v1/images \
  -H "Authorization: Bearer $MUSAVIR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"prompt":"A lone figure on a rooftop at dusk, 35mm, cinematic"}'
Response
HTTP/1.1 202 Accepted
Location: /v1/jobs/8f1c…
Retry-After: 2
X-Request-Id: 0d1f…

{
  "data": {
    "id": "8f1c…",
    "object": "generation",
    "kind": "image",
    "status": "queued",
    "progress": 0,
    "prompt": "A lone figure on a rooftop at dusk, 35mm, cinematic",
    "characterIds": [],
    "templateId": null,
    "resolution": "…",
    "ratio": "…",
    "durationSeconds": null,
    "audio": null,
    "credits": { "reserved": "…", "charged": "0.000000", "billingStatus": null },
    "createdAt": "2026-08-22T10:15:30.000Z",
    "startedAt": null,
    "completedAt": null,
    "pollAfterSeconds": 2,
    "requiredAction": null,
    "result": null,
    "failure": null
  }
}

Keep data.id. Location is the same job, and Retry-After is how long to wait before the first poll.

Poll the job

Read the job once
JOB_ID="8f1c…"

curl https://api.musavir.ai/v1/jobs/$JOB_ID \
  -H "Authorization: Bearer $MUSAVIR_API_KEY"

Poll about every two seconds, backing off toward ten for a long render. A job that is still moving tells you what to wait in pollAfterSeconds; a terminal or action-required job reports null. Render completion and billing completion are separate, as described below.

Poll until rendering stops or needs your action
while :; do
  STATUS=$(curl -s https://api.musavir.ai/v1/jobs/$JOB_ID \
    -H "Authorization: Bearer $MUSAVIR_API_KEY" | jq -r '.data.status')
  echo "$STATUS"
  case "$STATUS" in succeeded|failed|action_required) break ;; esac
  sleep 2
done

Three statuses end the loop. succeeded and failed are terminal for rendering. action_required means the job is waiting on you; inspect requiredAction.type before choosing its next call. queued and running mean keep polling. A running job can still be rendering or securing its finished output; retain the job id instead of treating a short client timeout as a failed generation.

A finished job
{
  "data": {
    "id": "8f1c…",
    "object": "generation",
    "kind": "image",
    "status": "succeeded",
    "progress": 100,
    "credits": { "reserved": "…", "charged": "…", "billingStatus": "settled" },
    "completedAt": "2026-08-22T10:16:04.000Z",
    "pollAfterSeconds": null,
    "requiredAction": null,
    "result": {
      "url": "https://…",
      "downloadUrl": "https://…",
      "contentType": "image/png",
      "expiresAt": "2026-08-22T11:16:04.000Z"
    },
    "failure": null
  }
}

Handle the requested action

approve_character_sheets is the existing video flow: review the sheets in requiredAction.previews, then call POST /v1/jobs/{id}/actions/approve with videos:generate. That action is unchanged.

confirm_current_inputs pauses an older image or video job without a trusted input snapshot. Display the current job prompt/specification and requiredAction.previews in their returned order; ensure the previews load so the user can review them. Show requiredAction.estimatedCredits and marginMultiplier alongside the existing reservation. The estimate is an advisory JSON number (or null), not a final accounting amount or an additional reservation. If it is unavailable or a preview cannot be reviewed, do not confirm; refresh the job or ask for help.

Only after explicit user approval, echo that response’s inputFingerprint to POST /v1/jobs/{id}/actions/confirm-inputs. The key needs jobs:read plus the matching images:generate or videos:generate scope. This resumes the same job and hold; it does not create or fund another job. A stale review returns 409: re-read and get approval again, not an automatic approval of newly changed inputs. Retrying the same accepted fingerprint is safe.

Confirm only the inputs the user reviewed
# Only after the user reviewed the current previews and estimate.
# Copy inputFingerprint from that SAME requiredAction response.
curl -X POST https://api.musavir.ai/v1/jobs/$JOB_ID/actions/confirm-inputs \
  -H "Authorization: Bearer $MUSAVIR_API_KEY" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --arg fingerprint "$REVIEWED_INPUT_FINGERPRINT" \
        '{inputFingerprint:$fingerprint}')"

Download the result

A succeeded job carries two links to the same object. result.url displays it; result.downloadUrl is signed with a filename so a browser saves it instead of playing it.

Download the finished image
DOWNLOAD_URL=$(curl -s https://api.musavir.ai/v1/jobs/$JOB_ID \
  -H "Authorization: Bearer $MUSAVIR_API_KEY" | jq -r '.data.result.downloadUrl')

curl -L -o musavir-image.png "$DOWNLOAD_URL"

What to do next

  • Attach your own images to a prompt: reference uploads.
  • Put a consistent face in every render: characters.
  • Make retries safe before you go to production: idempotency and retries.
  • Video works the same way — POST /v1/videos, same job, same poll. Read GET /v1/capabilities for the durations and resolutions it accepts.
  • When something returns a 4xx, every code is listed with a fix on the errors page.