Generations

Follow a Direct API generation to its final status by long-polling, read several at once, and know when to poll.

A generation is one unit of asynchronous work: one image, one video, one audio track, one render. Tools with returns_generation: true start one or more and answer at once with their ids. Follow them by polling, by webhook, or both: use a webhook as the main signal and an occasional poll as the safety net.

Statuses

queued ──► in_progress ──► succeeded
   │             │
   │             ├──────► failed
   └─────────────┴──────► cancelled
StatusFinalMeaning
queuednoaccepted, waiting for model capacity
in_progressnothe model is working
succeededyesassets holds the output
failedyeserror says why; the charge is refunded and billing.credits_refunded says how much
cancelledyesstopped before it produced output; the charge is refunded

Treat any status you do not recognise as not final and keep following the generation; new intermediate statuses may be added.

The generation object

{
  "object": "generation",
  "id": "0192a7b3-a1c4-7e02-8f9d-1a2b3c4d5e6f",
  "tool": "generate_video",
  "status": "succeeded",
  "project_id": "01928f4e-5b7c-7d2a-9e11-3c4b5a6d7e8f",
  "model_id": "mdl_7rk2pq9vxa4m",
  "created_at": "2026-10-25T09:14:03.120Z",
  "completed_at": "2026-10-25T09:15:41.902Z",
  "error": null,
  "billing": {
    "estimated_credits": 120,
    "credits_charged": 96,
    "credits_refunded": 0,
    "credits_are_final": true,
    "credits_settle_on_output_duration": true
  },
  "assets": [
    {
      "id": "0192a7b4-0f1e-7d3c-9b8a-7c6d5e4f3a2b",
      "type": "video",
      "name": "Bicycle hero shot",
      "mime_type": "video/mp4",
      "width": 1920,
      "height": 1080,
      "duration_seconds": 8,
      "url": "https://mcp.morphic.com/m/Xk29dPq",
      "url_expires_at": null,
      "studio_url": "https://studio.morphic.com/…",
      "final_generation_expires_at": null
    }
  ],
  "content_left_out": [],
  "render_warnings": [],
  "metadata": { "order_id": "4411" },
  "poll_after_seconds": null
}
FieldMeaning
errornull, or { "error_code": "…", "message": "…" } on failed. The message is safe to show your users.
billingwhat this generation cost; see Credits and usage
assets[].idthe asset id other tools take as input (image_asset_id, video_asset_id, …)
assets[].urla short link to download the media; it does not expire, and url_expires_at is null
assets[].studio_urlopens the asset in Morphic Studio for your team
assets[].final_generation_expires_atdraft videos only: until when finalize_video_draft can render it at full resolution
content_left_out, render_warningscomposes and timeline exports only: what the output could not include, and why
poll_after_secondswhile not final, how long to wait before the next read; null once final

Read one generation

GET /v1/direct/generations/{id} answers 200 with { "generation": { … } }.

curl -s "https://mcp.morphic.com/v1/direct/generations/$ID?wait=45" \
  -H "Authorization: Bearer $MORPHIC_API_KEY"
  • wait is optional, 0 to 45 seconds. The request is held open until the generation's status changes or it finishes, then answers at once.
  • When the wait runs out, it answers with the current state. A value above 45 is clamped to 45.
  • A generation of another organization, or one this key may not read, answers 404, never 403, so ids cannot be probed.

Read several generations

POST /v1/direct/generations/batch-get reads up to 10 generations in one request.

curl -s https://mcp.morphic.com/v1/direct/generations/batch-get \
  -H "Authorization: Bearer $MORPHIC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ids": ["0192a7b3-…", "0192a7b3-…"], "wait": 30}'
  • With wait, the request answers as soon as any of them changes status or all of them are final.
  • The response is { "generations": [ … ], "not_found": ["…"] }, in the order asked.

How often to poll

Each start and each non-final read carries poll_after_seconds. Wait at least that long before the next read, then back off.

OutputFirst poll afterTypical time to final
image, image edit, upscale, image operations5 s5 to 60 s
speech, sound effect, transcription10 s10 to 90 s
music, dialogue20 s30 s to 3 min
video, video edit, lip sync, renders30 s1 to 10 min, longer under load

A long-poll (wait=45) costs one read however long it is held, so it is both the fastest and the cheapest way to poll. Set your HTTP client's timeout above the wait: 60 s for wait=45.

import time, requests

def wait_for(generation_id, key):
    headers = {"Authorization": f"Bearer {key}"}
    url = f"https://mcp.morphic.com/v1/direct/generations/{generation_id}"
    while True:
        r = requests.get(url, params={"wait": 45}, headers=headers, timeout=60)
        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", "5")))
            continue
        r.raise_for_status()
        gen = r.json()["generation"]
        if gen["status"] in ("succeeded", "failed", "cancelled"):
            return gen
        # A long-poll already waited; only sleep if the server asks for more.
        time.sleep(max(0, (gen.get("poll_after_seconds") or 0) - 45))

Polling limits

LimitValueOn breach
Reads of one generation by one key30 per minute429, Retry-After
Reads of all generations by one key1,200 per minute429, Retry-After
wait45 secondsclamped
Ids per batch read10400

A batch read counts once per id against both limits. At the recommended cadence one key can follow several hundred generations in flight at once; if you follow more, use webhooks.


If you have any trouble or have any questions, feel free to reach out. We're happy to help. Write to us at support@morphic.com.

On this page