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

# Renders

> Compose and render video from a timeline of elements.

<Tip>
  Prefer to see it first? The [Playground](/renders/playground) renders your composition in the
  browser with WebGPU — the same renderer as the API — so you can preview and copy the exact
  `POST /v1/renders` request before spending a render.
</Tip>

## How renders work

1. **Provide your media** (if needed) — pass a `source_url` you already host on a CDN, upload local files with [`POST /v1/uploads`](/introduction/uploads), or have Framelane copy a public URL or cloud share link into storage for you by setting [`ingest_external: true`](/introduction/uploads#importing-public-urls) on the render. See [Uploading media](/introduction/uploads).
2. **Submit** a `POST /v1/renders` request with your composition — dimensions, elements, transitions, and output settings.
3. **Job accepted** — the API returns `202 Accepted` with a `render_*` job ID and `status: queued`. When the render copies in external files (`ingest_external`), it starts as `ingesting` and transitions to `queued` automatically once every file is ready.
4. **GPU engine processes** — the Framelane Renderer runs on dedicated GPU hardware.
5. **Wait for completion** — poll `GET /v1/renders/{id}` until `status` is terminal, or receive a `render.completed` / `render.failed` webhook.
6. **Download** — use `output.url` from the job response, or `GET /v1/renders/{id}/download` for a fresh signed redirect.

## Job lifecycle

```
(ingesting)  →  queued  →  processing  →  completed
                                       ↘  failed
                                       ↘  cancelled
```

| Status       | Description                                                                                                                                                                   |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ingesting`  | Copying public source files into Framelane storage (only when [`ingest_external`](/introduction/uploads#importing-public-urls) is set); transitions to `queued` automatically |
| `queued`     | Waiting in the render queue                                                                                                                                                   |
| `processing` | GPU engine is compositing                                                                                                                                                     |
| `completed`  | Artifact available at `output.url`                                                                                                                                            |
| `failed`     | See `error.code` for cause                                                                                                                                                    |
| `cancelled`  | Stopped via `DELETE /v1/renders/{id}`                                                                                                                                         |

## Idempotency

Every render submission should include an `Idempotency-Key` header to safely retry without double-submitting:

```http theme={null}
Idempotency-Key: my-project-render-v3
```

If you resubmit the same key within 24 hours:

* **Same body** → `200 OK` with the existing job (no new charge)
* **Different body** → `409 Conflict`

## Endpoints

| Method   | Path                        | Description                     |
| -------- | --------------------------- | ------------------------------- |
| `POST`   | `/v1/renders`               | Submit a render job             |
| `GET`    | `/v1/renders`               | List renders (cursor-paginated) |
| `GET`    | `/v1/renders/{id}`          | Get a single render             |
| `DELETE` | `/v1/renders/{id}`          | Cancel a queued render          |
| `GET`    | `/v1/renders/{id}/download` | Redirect to signed artifact URL |

## Minimal example

```json theme={null}
{
  "width": 1920,
  "height": 1080,
  "duration": 10.0,
  "frame_rate": 30,
  "output_format": "mp4",
  "elements": [
    {
      "type": "video",
      "id": "clip1",
      "source_url": "https://cdn.example.com/input.mp4",
      "time": 0,
      "in_point": 0,
      "out_point": 10
    },
    {
      "type": "text",
      "id": "title",
      "text": "Hello World",
      "time": 0,
      "duration": 5,
      "x": "50%",
      "y": "50%",
      "font_size": 72,
      "text_color": "#ffffff"
    }
  ]
}
```

<Note>
  The correct field names are `time` (start on the output timeline), `duration` (how long the element is visible), and `text_color` (not `color`). Using wrong field names results in a `422` validation error.
</Note>
