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

# Requests and lifecycle

> How a Mage API request moves from submission to result: the request object, its five statuses, how to poll, idempotency keys for safe retries, cancellation, and what the result contains.

The API is asynchronous. A submit returns at once with a request object, the generation runs in the background, and the same object, read from `status_url`, reports progress and the result.

## The request object

The submit response, a status read, and a cancel response all return this shape.

| Field          | Type           | Meaning                                                                                             |
| -------------- | -------------- | --------------------------------------------------------------------------------------------------- |
| `request_id`   | string         | The request's id. It is also the id in `status_url` and `cancel_url`.                               |
| `status`       | string         | One of the [statuses](#statuses) below.                                                             |
| `architecture` | string         | The architecture the request generates with, as in the endpoint path.                               |
| `model_id`     | string or null | The model variant, when the architecture has variants.                                              |
| `created_at`   | ISO 8601       | When the request was accepted.                                                                      |
| `updated_at`   | ISO 8601       | When it last changed.                                                                               |
| `billing`      | object         | `mode` is always `gems`; `gems_charged` is the charge; `gems_refunded` appears on a failed request. |
| `result`       | object or null | The output once `status` is `completed`. See [the result](#the-result).                             |
| `error`        | object or null | `code` and `message` once `status` is `failed`.                                                     |
| `status_url`   | URL            | Where to poll.                                                                                      |
| `cancel_url`   | URL            | Where to `POST` to stop the request.                                                                |

Use the URLs the API returns rather than building them yourself.

## Statuses

| Status        | Meaning                                                                                                                     | Final |
| ------------- | --------------------------------------------------------------------------------------------------------------------------- | ----- |
| `queued`      | Mage accepted the request and the generation backend has not confirmed it yet. Most submit responses are already past this. | No    |
| `in_progress` | The generation is running.                                                                                                  | No    |
| `completed`   | The output is ready in `result`.                                                                                            | Yes   |
| `failed`      | No output. `error` says why and `billing.gems_refunded` says what came back. See [errors](/api/errors).                     | Yes   |
| `cancelled`   | The request was stopped through `cancel_url`. Gems are not returned.                                                        | Yes   |

Output that Mage's content policy forbids reports as `failed` with the code `content_blocked` and no URL, so you never have to inspect moderation flags to learn that a request produced nothing.

## Polling

Poll `status_url` until the status is final. Start with a short interval, back off, and add a little jitter so a batch of requests does not poll in step.

* Images usually complete within seconds; video within minutes. Start at 2 seconds for an image and 5 seconds for video, multiply by 1.5 after each read, and cap the interval at 15 seconds.
* Set a deadline of your own for each request, generous for video, and treat a request that passes it as something to investigate rather than to resubmit.
* A status read is safe to retry on a network error or a `5xx`. A `401` or `404` will not change on retry; stop and check the key and the id.

<CodeGroup>
  ```python Python theme={null}
  import random
  import time

  import requests

  FINAL = {"completed", "failed", "cancelled"}
  headers = {"Authorization": f"Bearer {api_key}"}
  delay = 2.0

  while True:
      response = requests.get(status_url, headers=headers, timeout=30)
      response.raise_for_status()  # a 401 or 404 will not change; a 5xx may be retried
      request = response.json()
      if request["status"] in FINAL:
          break
      time.sleep(delay + random.uniform(0, 0.5))
      delay = min(delay * 1.5, 15.0)
  ```

  ```javascript JavaScript theme={null}
  const FINAL = new Set(['completed', 'failed', 'cancelled']);
  let delay = 2000;
  let request;

  for (;;) {
    const response = await fetch(statusUrl, {
      headers: { Authorization: `Bearer ${apiKey}` },
    });
    if (!response.ok) {
      throw new Error(`status read failed with ${response.status}`);
    }
    request = await response.json();
    if (FINAL.has(request.status)) break;
    await new Promise((resolve) =>
      setTimeout(resolve, delay + Math.random() * 500)
    );
    delay = Math.min(delay * 1.5, 15000);
  }
  ```
</CodeGroup>

## Retrying a submit safely

A submit charges Gems, so a client that times out while waiting for the response must not simply send it again. Send an `Idempotency-Key` header instead: a string of 1 to 255 characters of your choosing, scoped to your API key. A retried submit with the same key returns the original request with `200` and charges nothing.

The key identifies the first submission made with it, whatever body a later retry carries; the body is not compared. Derive the key from the request it protects, such as a hash of the payload or a job id that maps to exactly one payload, and never reuse it for a different request.

A key is consumed by the first submission Mage records under it, including one it then refuses: an error envelope that carries `request_id` means the same key will replay that failed request rather than submit a new one. Keep the key for transport retries, when you got no response at all, and use a new key for a new attempt after a refusal.

## Cancelling

`POST` to `cancel_url` to stop a live request. The run is marked cancelled and the generation backend is asked to stop; the response is the request's state afterwards. Gems are not returned for a cancelled request, as in the app.

Cancelling a request that already completed or failed is refused with `409 request_finished`. Cancelling a request that is already cancelled does nothing and returns its state.

## The result

| Field             | Meaning                                                                                                            |
| ----------------- | ------------------------------------------------------------------------------------------------------------------ |
| `type`            | `image` or `video`.                                                                                                |
| `url`             | Where to download the output.                                                                                      |
| `width`, `height` | Dimensions in pixels.                                                                                              |
| `seed`            | The seed the generation ran with. Send it back as `seed` to reproduce the output.                                  |
| `expires_at`      | When `url` stops working: 30 days after the request for temporary media, or null for media saved permanently.      |
| `moderation.nsfw` | Whether moderation flagged the output as NSFW. Forbidden output is never returned; it reports as a failed request. |

Download what you want to keep. A completed request stays `completed` after its media expires; only the URL stops working.
