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

# Render

> A render is a generated output file — a video (MP4 / MOV) or audio (M4A / MP3) deliverable produced from a source asset, optionally trimmed to a cut.

## What is a render

A render is an asynchronous job that turns a source asset into a
downloadable deliverable — a **video** (`mp4` / `mov`) or an **audio**
file (`m4a` / `mp3`), optionally trimmed to one of the asset's
[cuts](/concepts/cut). It's its own entity with its own lifecycle, and
you can have many renders off one source (a 720p and a 1080p, a full
video and an audio-only cut-down).

## Triggering a render

```bash theme={null}
curl https://api.roughy.ai/v1/renders \
  -H "Authorization: Bearer $ROUGHY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "asset_id": "<asset_id>",
    "cut_id": "<cut_id>",
    "output": {
      "format": "mp4",
      "resolution": "1080p"
    }
  }'
```

The response is the render row (`state: "pending"`); poll
`GET /v1/renders/{id}` until `completed`, or subscribe to the
`render.completed` / `render.failed` [webhooks](/concepts/webhooks).

### Output spec

| Field           | Values                                      | Required |
| --------------- | ------------------------------------------- | -------- |
| `format`        | `mp4`, `mov` (video) · `m4a`, `mp3` (audio) | yes      |
| `resolution`    | `720p`, `1080p`, `4k` — video only          | no       |
| `video_bitrate` | `5`, `10`, `20`, `50` (Mbps) — video only   | no       |
| `audio_bitrate` | `128`, `192`, `256`, `320` (kbps)           | no       |

`format` picks the container and the stack: `mp4` / `mov` are video
(H.264 + AAC, the same encode in a different container); `m4a` (AAC)
and `mp3` are audio-only outputs.

**Resolution is an aspect-preserving tier**, not a fixed width×height.
`720p` / `1080p` / `4k` target the **shorter** edge and scale the
source to it without padding, **capped to the source** — a render
never upscales. A landscape source stays landscape, a vertical source
stays vertical. Omit `resolution` to render at the source resolution.
`resolution` and `video_bitrate` apply to video formats only.

**Bitrate is a ladder, and optional.** Omit `video_bitrate` /
`audio_bitrate` for `Auto` (a sensible default for the tier, capped to
the source bitrate); set an explicit step to override it. An explicit
value above the source is capped to the source — encoding above it
only adds bytes.

### Applying a cut

A render addresses a specific cut by `cut_id` (cuts are
[1:N](/concepts/cut) per asset). Its **presence** applies that cut:

* `cut_id` present — apply that cut's kept-segment plan. The cut must
  belong to the asset and be `completed`.
* `cut_id` omitted — render the full source.

A `cut_id` that names a cut of this asset which isn't `completed` yet
returns **409** `modifier_not_ready` — retry once the cut finalizes.

### Source state: video vs audio

What a render needs depends on the output form:

* A **video** output reads the full source video, so the asset must be
  `ready`. Rendering video in any other state returns **409**
  `asset_not_ready`.
* An **audio** output reads only the asset's audio, so it gates on the
  same **`cuttable`** capability as a [cut](/concepts/cut) — a
  not-cuttable asset returns the precise **409** `asset_processing` /
  `asset_payment_required` / `asset_failed`. For a video whose audio
  was extracted client-side, an audio render is therefore available
  while the source video is still uploading.

### Validation

A request that's well-formed but invalid for the target asset returns
**422** `validation_error`:

* a video `format` (`mp4` / `mov`) requested for an audio-only asset;
* `resolution` or `video_bitrate` on an audio `format`;
* a `resolution` tier or `video_bitrate` above a known source;
* a `cut_id` that isn't a cut of this asset.

## Idempotency

A render is keyed on the content signature `(asset, cut, output)`:

* A repeat request with the same signature returns the existing render
  (**200**) — no second render, no second charge.
* A different `output` (e.g. `1080p` instead of `720p`, or `mp3`
  instead of `m4a`) is a separate render (**201**).
* A `failed` render with the same signature is replaced on retry.

Re-runs are free — the [credit](/concepts/credit-ledger) is charged
once per source asset, not per render.

## Downloading

`GET /v1/renders/{id}` includes a short-lived signed `download_url`
once `state` is `completed`. The list endpoint omits it — fetch the
render to act on it. The URL is valid for about 15 minutes; re-fetch
the render for a fresh one rather than caching it.

## Cancelling

```bash theme={null}
curl -X DELETE https://api.roughy.ai/v1/renders/{id} \
  -H "Authorization: Bearer $ROUGHY_API_KEY"
```

Cancel is a **hard delete**: the render row and any partial output are
removed (`204`). An in-flight render is stopped within seconds. To
render again, POST `/v1/renders` afresh.

## Related concepts

* [Cut](/concepts/cut) — the kept-segment plan a render applies.
* [Asset](/concepts/asset) — the source a render reads.
* [Webhooks](/concepts/webhooks) — push delivery for render terminal states.
* [Credit ledger](/concepts/credit-ledger) — why renders are free.
