Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.moda.app/llms.txt

Use this file to discover all available pages before exploring further.

Versioning

Moda’s API uses a calendar-dated version string on the Moda-Version request header. Pinning guarantees your wire shape stays stable.

Supported versions (as of 2026-04-24)

VersionRoleStatusSunsets
2026-05-01Canonical (newest)Latest response shape
2026-04-12Default (legacy)What unpinned traffic resolves to2026-05-12
  • Canonical = routes emit this shape natively. Pin this in production.
  • Default = what unpinned requests get. Advances to the next-newest supported version on sunset dates.
  • Sunset = after this date, pinning the sunset version returns 400 unsupported_version.

Sending the header

Moda-Version: 2026-05-01
Pin explicitly on every request. Don’t rely on the default.

Omitting

If you omit the header, the server resolves to the current default. Integrations keep working across version bumps until a sunset advances the default. For production, pin.

Unknown version

{
  "error": {
    "type": "invalid_request",
    "code": "unsupported_version",
    "message": "Moda-Version '2026-01-01' is not supported. Supported versions: 2026-04-12, 2026-05-01.",
    "details": {
      "requested": "2026-01-01",
      "supported": ["2026-04-12", "2026-05-01"]
    },
    "request_id": "019d8996-…"
  }
}
Read details.supported — that’s the ground truth, and it updates automatically.

Response header

Every response carries the resolved version:
HTTP/1.1 200 OK
Moda-Version: 2026-05-01
If you omit the request header, read the response’s Moda-Version to know which shape you got.

Canonical (2026-05-01) vs legacy (2026-04-12)

Task endpoints

Legacy flat shape (2026-04-12):
{
  "job_id": "task_01HT9...",
  "status": "completed",
  "canvas_id": "cvs_...",
  "canvas_url": "https://...",
  "conversation_id": null,
  "task": "Create a sales deck",
  "progress_percent": 100,
  "is_terminal": true,
  "can_export": true,
  "retry_after_seconds": null,
  ...
}
Canonical Task envelope (2026-05-01):
{
  "id": "task_01HT9...",
  "kind": "design",
  "status": "succeeded",
  "input": { "prompt": "Create a sales deck" },
  "result": { "canvas_id": "cvs_...", "canvas_url": "https://..." },
  "progress": null,
  "error": null,
  "credits": { "credits_used": 5, "credits_remaining": 12 },
  "links": { "self": "/v1/tasks/...", "cancel": null, "canvas": "..." },
  "retry_after_ms": null
}
Migration map:
LegacyCanonical
response.job_idresponse.id
response.status == "completed"response.status == "succeeded"
response.status == "cancelled"response.status == "canceled"
response.canvas_idresponse.result.canvas_id
response.canvas_urlresponse.result.canvas_url
response.conversation_idresponse.result.conversation_id
response.taskresponse.input.prompt
response.progress_percentresponse.progress.percent
response.current_stepresponse.progress.step
response.error (string)response.error.message (object)
response.is_terminalderived: status in ("succeeded","failed","canceled","expired")
response.can_exportderived: status == "succeeded" && result.canvas_id
response.retry_after_secondsresponse.retry_after_ms / 1000

List endpoints: offset → cursor

Legacy:
{ "canvases": [...], "total": 123, "offset": 20, "limit": 20, "has_more": true }
Canonical:
{ "data": [...], "next_cursor": "eyJ2..." }
Iterate until next_cursor === null. See pagination.md.

Webhook events

Legacy job.* names are retired in favor of task.* / export.*. Spelling changed: cancelledcanceled (matches the PublicTaskStatus.CANCELED enum). Non-terminal states (queued, running, expired) no longer fire webhooks.
LegacyCanonical
job.completedtask.succeeded
job.failedtask.failed
job.cancelledtask.canceled
job.dead_lettertask.failed with data.error.retryable: false
job.runningremoved (poll /v1/tasks/{id})
job.queuedremoved

What triggers a new version

Only breaking response-shape changes. Additive changes (new endpoints, new optional params, new response fields) ship at the current canonical version without a bump. Breaking:
  • Removing / renaming a response field
  • Removing an endpoint or path
  • Changing a field’s type
  • Tightening validation
  • Changing status vocabulary
Additive (no bump):
  • New endpoint / path / optional param / response field
  • New error code within an existing type
Tolerate unknown fields in your client.

Version-bump cadence

  • New canonical arrives.
  • Previous default stays supported for a sunset window (usually 2–3 weeks).
  • On the sunset date, the older version retires; default advances.
  • If you pinned, you upgrade on your schedule.

Recommendations

  • Pin Moda-Version explicitly on every production request.
  • Log the response Moda-Version so you can tell which shape your traffic is actually hitting.
  • When you see 400 unsupported_version, read details.supported from the error.
  • Subscribe to the Moda changelog for sunset dates.

Common wrong guesses

  • Omitting Moda-Version in production. The default will advance on sunset and your response shape will silently change.
  • Parsing legacy job_id / canvas_id from canonical responses. They’re nested under id and result.canvas_id.
  • Spelling cancelled with two Ls. Canonical spelling is canceled (one L). cancelled is a legacy artifact.
  • Assuming a sunset version still works. After the sunset date, pinning it returns 400.

Upstream

docs.moda.app/api/versioning