# Create a render

> Render an exterior, interior, masterplan, landscape, plan or product from an image, or from a prompt alone.

Source: https://developers.mnml.ai/docs/renders

`POST https://api.mnml.ai/v2/renders`

## Example request

**curl**

```bash
curl -X POST https://api.mnml.ai/v2/renders \
  -H "Authorization: Bearer $MNML_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "engine": "v4.6-ultra",
    "mode": "exterior",
    "image": "https://example.com/massing.png",
    "prompt": "Timber facade, late afternoon, olive trees",
    "aspect_ratio": "16:9"
  }'
```

**TypeScript**

```ts
const res = await fetch("https://api.mnml.ai/v2/renders", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.MNML_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "engine": "v4.6-ultra",
    "mode": "exterior",
    "image": "https://example.com/massing.png",
    "prompt": "Timber facade, late afternoon, olive trees",
    "aspect_ratio": "16:9"
  }),
});

const data = await res.json();
```

**Python**

```python
import os, requests

res = requests.post(
    "https://api.mnml.ai/v2/renders",
    headers={"Authorization": f"Bearer {os.environ['MNML_API_KEY']}"},
    json={
      "engine": "v4.6-ultra",
      "mode": "exterior",
      "image": "https://example.com/massing.png",
      "prompt": "Timber facade, late afternoon, olive trees",
      "aspect_ratio": "16:9"
    },
)

data = res.json()
```

## Example response (202)

```json
{
  "success": true,
  "data": {
    "id": "10482",
    "status": "queued",
    "charged_usd": 0.2475,
    "credits_charged": 25,
    "replayed": false,
    "notes": [],
    "ids": [
      "10482"
    ]
  }
}
```

Returns `202` straight away with the job and what it charged. Poll [GET /v2/jobs/{id}](https://developers.mnml.ai/docs/jobs) for the result; a job that fails is refunded. The price is the engine’s: see [Pricing](https://developers.mnml.ai/docs/pricing).

Anything accepted but not applied, such as a setting that does not fit the engine, comes back in `notes` rather than being dropped silently. A field the API does not know is refused before anything is charged.

## Sending the image

The image goes in the same call, with no upload step. Every create call takes it the same three ways, and so does each reference, an edit’s `mask` and a video’s `end_frame`:

- A file on disk: send the call as multipart form data, with the file as the `image` part and every other field as a text part. Repeat `references` for several references.
- A link: `"image": "https://…"` in JSON. mnml fetches it.
- Bytes in your code: `"image": "data:image/jpeg;base64,…"` in JSON, or bare base64.

**From a file**

```
curl -X POST https://api.mnml.ai/v2/renders \
  -H "Authorization: Bearer $MNML_API_KEY" \
  -F image=@site-photo.jpg \
  -F prompt="Timber facade, late afternoon, olive trees" \
  -F references=@material-board.jpg
```

In a form, numbers and `true` / `false` are written as text, and `settings` or a `region` as JSON. Images are JPEG, PNG or WebP, up to 15 MB each. To work on an image mnml already made for you, pass its `job_id` instead: nothing is downloaded or sent again.

## Getting the result in the same call

Add `?wait=90` and the answer waits up to that many seconds (90 at most) while the render runs. If it finishes in time, the answer is a `200` and `jobs` holds every job with its `outputs`. If it is still running, the answer is the usual `202` with `jobs` as they stand. Then [follow the job as a stream](https://developers.mnml.ai/docs/jobs#stream): one connection that stays open until the render is done, however long it takes. How long a render takes depends on the engine: compare the engines on the [models page](https://developers.mnml.ai/docs/models).

**One call**

```
curl -X POST "https://api.mnml.ai/v2/renders?wait=90" \
  -H "Authorization: Bearer $MNML_API_KEY" \
  -F image=@site-photo.jpg \
  -F prompt="Timber facade, late afternoon, olive trees"

# 200 → data.jobs[0].outputs[0].url is the render
```

For a slower engine, a video, or a script that should simply block until the result is ready, skip `wait`: start the job, then open its stream. It sends the job each time it changes and ends with `done` and the finished job.

**Start, then follow**

```
JOB_ID=$(curl -s -X POST "https://api.mnml.ai/v2/renders" \
  -H "Authorization: Bearer $MNML_API_KEY" \
  -F image=@site-photo.jpg \
  -F prompt="Timber facade, late afternoon, olive trees" | jq -r .data.id)

curl -N "https://api.mnml.ai/v2/jobs/$JOB_ID/events" -H "Authorization: Bearer $MNML_API_KEY"

# event: done → data.outputs[0].url is the render
```

## Headers

- `Authorization` (string, required): `Bearer mk_live_…`: your API key. See [Authentication](https://developers.mnml.ai/docs/authentication).
- `Content-Type` (string, required): `application/json`, or `multipart/form-data` to send a file.
- `Idempotency-Key` (string): Your own key, 1 to 255 visible ASCII characters. A retry with the same key and request within 24 hours returns the first answer instead of running again.

## Query parameters

- `wait` (integer): Hold the answer up to this many seconds (1 to 90) while the render runs, and answer with the finished jobs in `jobs`. Still running when the time is up: a 202; read the job with GET /v2/jobs/{id}?wait=90 until it settles.

## Body

- `engine` (string, default `v4.6-ultra`): An engine id or name from [GET /v2/engines](https://developers.mnml.ai/docs/engines).
- `mode` (string, default `exterior`): What the image shows. Without an image it defaults to `text-to-render`, which renders from the prompt alone. Values: `exterior`, `interior`, `masterplan`, `plan`, `landscape`, `product`, `text-to-render`.
- `prompt` (string, required): What to render: materials, light, season, context.
- `image` (string): The JPEG, PNG or WebP to work on, up to 15 MB: a public `https://` link we fetch, a `data:image/…;base64,` URI, or bare base64. With multipart form data, attach the file itself (`-F image=@house.jpg`). Send one of `image` or `job_id`.
- `job_id` (string): A finished job of yours: work on its output without downloading it first.
- `references` (string | object[]): Images whose style, materials, mood or geometry to follow, up to 15. Each is just the image (a link or base64, or with multipart a `references` file part, repeated), or an object, `{ image | job_id, mode }`, that also sets its `mode`. How many an engine takes is `capabilities.max_references` in [GET /v2/engines](https://developers.mnml.ai/docs/engines).
  - `image` (string): The reference image: a public `https://` link we fetch, a `data:image/…;base64,` URI, or bare base64. With multipart form data, attach the file itself. Send one of `image` or `job_id`.
  - `job_id` (string): The reference image, as a finished job of yours.
  - `mode` (string, default `style`): What to take from it: its `style`, a `material`, the `atmosphere`, the `color` or the `geometry`. `auto` lets the engine decide. Values: `auto`, `style`, `material`, `atmosphere`, `color`, `geometry`.
- `settings` (object): Studio’s settings for this engine and mode, as name → value. A setting left out takes the value Studio opens on. A setting that does not apply is listed in `notes`, not applied.
- `aspect_ratio` (string, default `auto`): The frame. `auto` keeps the source image’s shape. Values: `auto`, `1:1`, `3:2`, `4:3`, `5:4`, `16:9`, `21:9`, `2:3`, `3:4`, `4:5`, `9:16`.
- `count` (integer, default `1`): Variations of this brief, 1 to 4. Each is its own job and charge; `ids` lists them. A given seed is stepped for each.
- `seed` (integer): Reuse a seed to stay close to an earlier result. Engines that take no seed say so in `notes`.
- `webhook_url` (string): A public `https://` URL to send this job’s settle event to, signed like every [webhook](https://developers.mnml.ai/docs/webhooks), on top of your account’s endpoints.

## Response

On success (`202`): Started. Poll GET /v2/jobs/{id} for the result. The JSON is `{ "success": true, "data": … }`, and inside `data`:

- `id` (string, required): The job. Read it at [GET /v2/jobs/{id}](https://developers.mnml.ai/docs/jobs). With `count`, the first of `ids`.
- `ids` (string[], required): Every job this call started, one per variation, in order.
- `status` (string, required): The job’s status when it was accepted, usually `queued`. Values: `queued`, `processing`, `succeeded`, `failed`, `canceled`.
- `charged_usd` (number, required): What this call charged, every variation together, in US dollars.
- `replayed` (boolean, required): True when this is the stored answer to an earlier identical request with the same `Idempotency-Key`: nothing ran or was charged again. See [Idempotency](https://developers.mnml.ai/docs/idempotency).
- `notes` (string[], required): What was accepted but not applied, in plain words: a setting that does not fit the engine, a seed it does not take. Empty when everything applied. When fewer variations started than `count` asked for, it says so here.
- `jobs` (object[]): Only with `?wait`: every job this call started, as [GET /v2/jobs/{id}](https://developers.mnml.ai/docs/jobs) reads it, outputs included. A `200` means all of them finished in time; a `202` means some are still running: read each with [GET /v2/jobs/{id}?wait=90](https://developers.mnml.ai/docs/jobs#waiting) until it settles.
  - `id` (string, required): The job’s id, as the create call answered it.
  - `status` (string, required): Where the job is. `succeeded`, `failed` and `canceled` are final: stop polling. Values: `queued`, `processing`, `succeeded`, `failed`, `canceled`.
  - `kind` (string, required): What the job makes: `render`, `edit`, `erase`, `upscale`, `enhance`, `bg-remove`, `extend` (an `outpaint`) or `video`.
  - `engine` (string | null, required): The engine’s mnml name for a render or an edit; `null` for an enhancement, an erase or a video.
  - `outputs` (object[], required): The results, once the job has `succeeded`; empty before that, and on failure.
    - `url` (string, required): A signed link to the file at [GET /v2/files/{id}](#files). It needs no key.
    - `media` (string, required): Whether the file is an image or a video. Values: `image`, `video`.
    - `expires_at` (string, required): When the link stops working, as an ISO 8601 time. Poll again for a fresh one, and download what you keep.
  - `charged_usd` (number, required): What the job charged when it started, in US dollars.
  - `refunded_usd` (number, required): What came back when the job failed or was cancelled, in US dollars; 0 otherwise.
  - `error` (object | null, required): Why the job failed or stopped; `null` otherwise.
    - `code` (string, required): A stable code to branch on. See [When a job fails](#errors). Values: `UNSAFE_CONTENT`, `NO_CHANGE`, `CANCELED`, `RENDER_FAILED`.
    - `message` (string, required): The same in plain words, for a person.
  - `created_at` (string, required): When the job was accepted, as an ISO 8601 time.
  - `completed_at` (string | null, required): When it reached a final status, as an ISO 8601 time; `null` while it runs.

**Response headers (5)**

- `X-Request-Id` (string): This request, for support and the console log.
- `RateLimit-Limit` (integer): Requests this key may make per minute.
- `RateLimit-Remaining` (integer): Requests left in the current minute.
- `RateLimit-Reset` (integer): Seconds until the current minute resets.
- `Idempotent-Replayed` (string): Present, as `true`, when this is the stored answer to an earlier request.

## Status codes

| Status | Meaning |
| --- | --- |
| 200 | Finished, within ?wait: every job, with its outputs, is in jobs. |
| 202 | Started. Poll GET /v2/jobs/{id} for the result. |
| 401 | The API key is missing, malformed, unknown or revoked. |
| 402 | The balance does not cover this call. |
| 409 | The Idempotency-Key was reused for a different request, or is still in progress. |
| 429 | A limit was reached: requests per minute, or a daily or concurrency limit. |
| Other | Any other error, in the same envelope. |

Every error answers `{ "success": false, "error": { "code", "message" } }`. What each code means, and what to do, is on [Errors](https://developers.mnml.ai/docs/errors).
