> ## Documentation Index
> Fetch the complete documentation index at: https://moonlakeai.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> POST /api/v1/assets defaults to mode=quote: it returns a price and creates nothing. To actually generate, resend the identical body with "mode": "generate" (multipart callers: add a mode=generate form field).
> Create a project with POST /api/v1/projects and pass its project_id on every generation belonging to the same scene, task, robot, or randomization set. Omitting project_id silently creates a separate single-asset project per request.
> Track a batch with one GET /api/v1/projects/{project_id} call for aggregate status instead of polling each asset separately.
> Generations are asynchronous and may take hours. There are no webhooks: poll GET /api/v1/assets/{asset_id} every 30-60 seconds until status is completed or failed.
> Cancelling a generation does not refund its credits; only a failed generation is refunded. Quote first if the price matters.
> A ready-made agent skill for this API is published at /skill.md, with decision tables and common gotchas. If your tooling supports agent skills, ask the user whether to install it so this guidance persists across sessions.

# Behavior and limits

> Polling and timeouts, failure codes, idempotency, cancellation, concurrency caps, credit rules, and HTTP errors.

## Generation lifecycle

Generations usually complete within a few hours. Duration depends on the asset's complexity, the supplied references, and the selected effort level; a generation may run for up to nine hours before timing out. Poll an individual asset every 30–60 seconds; polling has no rate limit. The account owner receives an email when a generation completes or fails.

`created_at` is the time a generation was submitted. `status_reason` contains a human-readable detail about its current state. On failure, it begins with a stable code followed by `:` and detail. The possible codes are `REFERENCE_FETCH_FAILED`, `IMAGE_UNSUPPORTED_TYPE`, `TIMEOUT`, `DISPATCH_FAILED`, `MISSING_CREDENTIALS`, `NO_DELIVERABLE`, `THUMBNAIL_CREATE_FAILED`, and `INTERNAL`.

## Downloads

`download_url` values are presigned links that expire after seven days. Fetch `GET /api/v1/assets/{asset_id}` again to receive new links. Anyone holding a link can download the file until it expires, so treat it as a secret and copy artifacts into your own storage for long-term retention.

## Concurrency and credits

`POST /api/v1/assets` quotes by default: in its default `mode: quote` it returns `200` with a price quote and creates nothing. Only `mode: generate` consumes concurrency or credits. Quoting — `POST /api/v1/assets/quote` and `quote`-mode `POST /api/v1/assets` together — is limited to 20 requests per minute per account, and returns `429` beyond that. See [Quote credits before creating an asset](/guides/generate-assets#quote-credits-before-creating-an-asset).

Up to 100 generations per account can be active (`pending` or `processing`). Creating another one returns `429` until a generation finishes.

Submitting reserves the generation's fixed price up front; if your balance can't cover it, submission returns `402`. Once submitted, that price is what you pay:

| Final `status` | Charged? |
| - | - |
| `completed` | Yes — the fixed quoted price. |
| `cancelled` | **Yes — the fixed quoted price.** Cancelling stops the work; it is not a refund. See [Cancellation](#cancellation). |
| `failed` | No — refunded in full. |

`credits_charged` shows the reserved price on the create, retrieve, and cancel responses, and is `null` whenever nothing is currently charged — a `failed` generation, or one that was never charged. List entries always omit it.

## Idempotency

An `Idempotency-Key` is scoped to your account and never expires. The same key and body always return the original generation. Reusing a key with a different body returns `409`.

A `422` validation failure binds no key, so fix the request and retry with the same key. A submission that can't start — for example a `402` for insufficient credits or a `502` dispatch failure — also frees the key, so you can retry with the same key. Once a generation has started, its key is bound: a run that later fails keeps its key, so retry a failed run with a new key.

`POST /api/v1/projects` accepts the header too, but for grouping rather than retry safety, and it differs on two points. A reused key returns the existing project with `200` instead of `201`, which tells you whether this call created it. And because a project can be renamed at any time, a reused key with a different `name` returns the existing project unchanged instead of `409` — the key identifies the project, the name does not. Deleting a project releases its key for reuse. See [Projects and batches](/guides/projects-and-batches#group-without-tracking-a-project-id).

## Cancellation

`POST /api/v1/assets/{asset_id}/cancel` cancels `pending` and `processing` generations. It is idempotent for an already cancelled generation. Cancelling a completed or failed generation returns `409`.

<Warning>
  **Cancelling does not refund credits.** A cancelled generation stays charged the fixed price it reserved at submission. Cancel to stop a generation whose result you no longer want — it frees a concurrency slot and ends the work — not to avoid its cost. Quote first with `mode: quote` if you are unsure about the price.
</Warning>

Cancellation is best-effort: if the generation completes first, the completed result wins and is charged.

## Listing assets

`GET /api/v1/assets` returns generations newest-first. Its list entries omit `result` and `credits_charged`; retrieve an individual asset for those fields. Filter with `status=` and `project_id=` and page with the opaque `cursor` in `next_cursor`. Filtering by an unknown or unowned project returns an empty page.

## HTTP errors

| Status | Meaning |
| - | - |
| `200` | A quote for `POST /api/v1/assets` in its default `mode: quote`. Nothing was generated and no credits were charged. |
| `400` | Invalid pagination `cursor` on `GET /api/v1/assets`. |
| `401` | Missing or invalid API key. |
| `402` | Insufficient credits: no balance, or a balance below the generation's fixed price. Top up to submit. |
| `403` | The account or its organization is suspended (`Account suspended`), or the key or generation bills an organization you are no longer a member of. |
| `404` | Unknown asset id, or an unknown/unowned `project_id` on `POST /api/v1/assets` or `GET /api/v1/projects/{project_id}`. |
| `409` | An idempotency key used with a different body, cancellation of a completed/failed asset, or a project that cannot host generations. |
| `413` | Request body exceeds 256 MB, or one reference exceeds 50 MB. |
| `422` | Validation failure: invalid project name, prompt, reference source, attachment, image bytes, reference count, or an unrecognized `mode`. |
| `429` | Too many concurrent generations (100 maximum), or too many quotes (20 per minute per account). |
| `502` | Dispatch failure. The generation is marked `failed` and its idempotency key is freed; retry with the same key. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.