Skip to main content

Task envelope

Every task-shaped operation — design, export (when async), remix, brand-kit extraction — returns the canonical Task envelope. Pin Moda-Version: 2026-05-01 to get this shape.

Full shape

Fields

Status state machine

Terminal: succeeded, failed, canceled, expired. Non-terminal: queued, running. Derive is_terminal = status in {"succeeded","failed","canceled","expired"}. Derive can_export = status == "succeeded" && result && result.canvas_id.

Discriminator — kind

Task shape is polymorphic on kind: Always check kind before reading result fields. On a succeeded design task, result.export carries the finished design already rendered to a file{url, format, status, page_count}, exported in the canvas’s category-default format. Read it directly instead of issuing a separate POST /v1/canvases/{id}/export. It is absent when auto-export was disabled (export_on_complete: {enabled: false}) or did not finish within the budget. Note: POST /v1/canvases/{id}/export today is synchronous — it returns a plain {url, format} payload, not a Task envelope. The export kind applies to webhook events (export.succeeded / export.failed) and to long-running export flows if/when they exist. See canvases-and-exports.md.

Delivery patterns

Pick one per task — don’t stack: Pass callback_url in the task body. Webhook fires on terminal state only. Requires moda_live_… API key auth (OAuth callers get 400). See webhooks.md.

2. Polling — simplest for short-lived callers

Respect retry_after_ms. Don’t poll faster.

3. Prefer: wait=<seconds> — for fast operations only

Add the header on any POST that returns a Task envelope. The server holds the response until terminal OR the wait budget expires.
Cap: 30 seconds (server-enforced, per RFC 7240 wait parameter). Valid use cases:
  • Brand-kit creation (POST /v1/brand-kits) — takes 10–30s
  • Remix without a prompt — synchronous on the backend
  • Short design tasks with model_tier: "lite" and a tight scope
Not valid for from-scratch design tasks. They take 2–10 minutes; wait=30 will time out and return the non-terminal envelope. Use webhooks or polling for design. Gateway / proxy warning: behind a 30s-timeout CDN or load balancer, don’t set wait=30 — you’ll hit the gateway timeout before the server times out, and the client sees an abrupt disconnect rather than the graceful envelope return. Use wait=20 or poll.

How Prefer: wait responds

  • If the task reaches terminal inside the budget: returns terminal envelope.
  • If the budget expires first: returns the current (non-terminal) envelope. Not an error.
You still need to branch on status — the wait header doesn’t change the return contract, only the latency.

Timing expectations

Set user expectations up front. Don’t block callers on multi-minute operations.

Common wrong guesses

  • Using Prefer: wait=30 on a design task from scratch. It almost always times out. Use webhooks or polling.
  • Polling faster than retry_after_ms. Burns your rate budget for no latency benefit.
  • Reading response.canvas_id on a canonical response. It’s response.result.canvas_id. The flat field is legacy (2026-04-12).
  • Expecting export kind from POST /v1/canvases/{id}/export. That endpoint is synchronous and does not return a Task envelope.
  • Treating expired as a failure. It is terminal (same as the others) but the cause is queue-depth or worker-starvation, not a user-actionable error — retry the task fresh.

Upstream