# Render and download the video

> Apply a template, generate the final MP4, follow the render and download one file or hundreds in a zip.

The clip the AI cuts is raw. The render turns the clip into a vertical MP4 with template, captions and effects, ready to post.

```text
apply_template (optional)  →  POST .../render  →  GET /renders/{id}  →  GET /renders/{id}/download
        edit                    202 queued          until completed        1-hour URL
```

## Apply a template

Templates are edits saved in Cut.Pro: framing, caption style, effects. List yours:

```bash
curl "https://api.cut.pro/api/v1/templates?filter=mine" \
  -H "X-Api-Key: $CUTPRO_API_KEY"
```

Each item carries `id`, `name`, `aspect_ratio` and `auto_add_captions`. `filter=public` lists Cut.Pro's.

To edit all of a submission's clips at once:

```bash
curl -X POST https://api.cut.pro/api/v1/clips/7412908365112823/submissions/7412908371004912/apply_template \
  -H "X-Api-Key: $CUTPRO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "template_id": "7398110254410021", "clip_ids": ["7412908412887301"] }'
```

```json title="202 Accepted"
{ "clips_count": 1 }
```

Without `clip_ids`, it applies to all clips. It runs in the background: each clip gets an `edit_id` in `GET .../clips` when it finishes. One batch per account at a time; another responds `429 BATCH_ALREADY_RUNNING`.

Simpler: send `template_id` already in `POST /clips` and every clip comes out edited.

## Render a clip

```bash
curl -X POST https://api.cut.pro/api/v1/clips/7412908365112823/submissions/7412908371004912/clips/7412908412887301/render \
  -H "X-Api-Key: $CUTPRO_API_KEY"
```

```json title="202 Accepted"
{
  "id": "7412910087712256",
  "edit_id": "7412910087700001",
  "status": "queued",
  "has_watermark": false,
  "output_resolution": "1080p",
  "from_cache": false,
  "download_url": null
}
```

With a template applied, the render uses the edit. Without a template, it uses the default layout. Keep the `edit_id`: it is what publishes the video and what generates a new file later.

If an identical render is already finished, the response is `200` with `from_cache: true` and `download_url` filled in. There is no queue and no waiting. Requesting the same clip while it renders responds `409 RENDER_ALREADY_IN_PROGRESS`.

A live clip has its own route: [`POST /monitors/{id}/broadcasts/{broadcastId}/clips/{clipId}/render`](/en/monitoring#get-the-clips-of-a-live-stream).

## Follow the render

```bash
curl https://api.cut.pro/api/v1/renders/7412910087712256 \
  -H "X-Api-Key: $CUTPRO_API_KEY"
```

Poll every 5 to 10 seconds. `progress` goes from 0 to 100, handy for a progress bar.

| `status` | Means |
|---|---|
| `waiting_quality` | Waiting for the source to become available in the best quality. It continues on its own, with no new call. |
| `queued` | In the queue. The plan defines how many renders run at the same time. |
| `active` | Rendering. |
| `completed` | Ready to download. |
| `failed` | Failed. Ask again with `POST /renders`. |
| `cancelled` | Canceled with `DELETE`. |
| `expired` | The file left storage. The edit remains: render again. |

## Download the MP4

```bash
curl https://api.cut.pro/api/v1/renders/7412910087712256/download \
  -H "X-Api-Key: $CUTPRO_API_KEY"
```

```json title="200 OK"
{ "url": "https://...", "filename": "The chorus everyone knows.mp4" }
```

> The URL is valid for 1 hour. Ask for a new one on every download instead of storing it. The file stays in storage for the plan's `render_expiry_hours`; after that, the route responds `410 RENDER_FILE_EXPIRED`.

## Render again

The edit never expires. To get the MP4 of a clip from months ago, render the `edit_id`:

```bash
curl -X POST https://api.cut.pro/api/v1/renders \
  -H "X-Api-Key: $CUTPRO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "edit_id": "7412910087700001" }'
```

The `edit_id` of a render, a clip or a post item works. The response has the same format as rendering a clip.

## Download several in a zip

**curl**

```bash
curl -X POST https://api.cut.pro/api/v1/renders/bulk-download \
  -H "X-Api-Key: $CUTPRO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "render_ids": ["7412910087712256", "7412910087712257"] }'

curl https://api.cut.pro/api/v1/renders/bulk-download/$JOB_ID \
  -H "X-Api-Key: $CUTPRO_API_KEY"
```

**Node.js**

```js
const headers = { "X-Api-Key": process.env.CUTPRO_API_KEY, "Content-Type": "application/json" };
const API = "https://api.cut.pro/api/v1";

const { id } = await fetch(`${API}/renders/bulk-download`, {
	method: "POST",
	headers,
	body: JSON.stringify({ render_ids: renderIds }),
}).then((response) => response.json());

let job;
do {
	await new Promise((resolve) => setTimeout(resolve, 5000));
	job = await fetch(`${API}/renders/bulk-download/${id}`, { headers }).then((response) => response.json());
} while (job.status === "queued" || job.status === "active");

if (job.status === "failed") throw new Error(job.error_code);
console.log(job.download_url);
```

- Up to 500 renders per zip. More than that responds `400 TOO_MANY_RENDERS`.
- Ids from another workspace are ignored. If none are left, `404 NO_VALID_RENDERS`.
- `download_url` appears in `completed` and is valid for 1 hour. The zip disappears at `expires_at`; after that, `410 JOB_EXPIRED`.

## List and delete

| Route | What it does |
|---|---|
| `GET /renders` | Lists the renders, from newest to oldest. Filter with `status`: `processing`, `completed`, `failed` or `expired`. |
| `DELETE /renders/{renderId}` | Cancels a render that is queued or running. On a finished render, deletes the record and the file. |
| `GET /renders/limits` | Renders running and queued, resolution, watermark and the plan's retention. See [Limits](/en/rate-limits#renders). |

## Errors

| Code | When |
|---|---|
| `CLIP_RESULT_NOT_FOUND` | The clip does not exist in this submission. |
| `EDIT_NOT_FOUND` | The `edit_id` is not from this workspace. |
| `RENDER_ALREADY_IN_PROGRESS` | The same clip or edit is already rendering. Follow the existing render. |
| `RENDER_FILE_EXPIRED` | The MP4 left storage. Render the `edit_id` again. |
| `RENDER_FAILED`, `RENDER_SETUP_FAILED` | Our failure when queuing. Try again. |
