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

# Transforms

> Make free Derived copies of an asset: a frame, a shorter clip, a crop, a LUT grade, color correction, a smaller size, or another format.

A transform makes a new **Derived** asset from an asset in your Library, using
a recipe. Transforms are free, and the original stays as it was. Use one to take
a still from a video, cut a clip down, or convert a file that a model refuses.

## Transform an asset

Send a recipe to `POST /v1/assets/{assetId}/transform`. The key needs the
`runs:create` and `runs:read` scopes, because the new asset's ID comes from reading
the run.

```bash theme={null}
curl https://api.zerogen.com/v1/assets/$ASSET_ID/transform \
  -H "Authorization: Bearer $ZEROGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 5c4b3a2f-1e0d-4c9b-8a7f-6e5d4c3b2a1f" \
  -H "Prefer: wait=60" \
  -d '{
    "recipe": {
      "trim": { "startSeconds": 2, "endSeconds": 8 },
      "resize": { "maxEdge": 1280 }
    }
  }'
```

| Field | Description |
| - | - |
| `recipe` | Required. What to make. See [Recipes](#recipes). |
| `projectId` | Optional. The project the copy goes into. Defaults to the source asset's project. |
| `folderId` | Optional. A folder in that project to add the copy to. |

## Follow the run

The work runs in the background, so a transform returns a
[run](https://docs.zerogen.com/developers/quickstart.md#run-statuses), not an asset. The response is `202` while the run
is unfinished and `200` once it has ended, and `Location` names the run.

To follow it, read `GET /v1/runs/{id}`. You can also send `Prefer: wait=N`, up
to 60 seconds, on either request to hold the response until the run ends.

When the run succeeds, `outputs[0].asset` is the new asset. Use its `id` as a
run input, or download it with `GET /v1/assets/{assetId}/download`.

A transform's `model` is `null`, and it's never charged.

## Reused work

If you already ran the same recipe on the same asset, for the same project and
folder, the response returns that run with `reused` set to `true` and starts
nothing. This applies to your runs that are unfinished or succeeded, whichever
surface started them, as long as the copy is still in that folder. A failed run
is never reused, so sending the request again retries it.

A retry with the same `Idempotency-Key` starts nothing and returns the run as it
is now.

## Recipes

A recipe has one form, chosen by the source asset. Each step is optional, but a
recipe needs at least one, and steps always apply in the order shown.

| Source | Steps, in order |
| - | - |
| Video, to a still | `extractFrame`, alone |
| Video, to a video | `trim`, `crop`, `despill`, `lut`, `adjust`, `resize`, `convert` |
| Image | `crop`, `resize`, `convert` |

For each step's fields and limits, see the
[endpoint reference](https://docs.zerogen.com/api-reference/overview.md). Crop coordinates are whole pixels
of the picture as it displays, the size an asset's `width` and `height` report.

## Apply a LUT

A LUT applies to video only. List your organization's newest 200 LUTs with
`GET /v1/luts`, which needs the `library:read` scope, and name one in the recipe:

```json theme={null}
{ "recipe": { "lut": { "lutId": "1f2e3d4c-5b6a-4978-8a9b-0c1d2e3f4a5b" } } }
```

Each LUT's `inputColor` says what footage its uploader made it for. ZeroGen can't
check this against your footage, so choose a LUT that matches: a log LUT looks
wrong on Rec.709 footage. People add and remove LUTs in ZeroGen, not through the
API.

`lut.strength` mixes the original back in: a percent from 1 to 100, where 100,
the default, applies the whole LUT.

## Correct color

`adjust` corrects a video's color after any LUT, so it trims what the LUT gave.
It takes `exposure`, `brightness`, `contrast`, `saturation`, `temperature`, and
`tint`, each a whole number from -100 to 100, where 0 changes nothing:

```json theme={null}
{
  "recipe": {
    "inputColor": "slog3-sgamut3cine",
    "lut": { "lutId": "1f2e3d4c-5b6a-4978-8a9b-0c1d2e3f4a5b", "strength": 80 },
    "adjust": { "exposure": 15, "temperature": -10 }
  }
}
```

`inputColor` says what the footage is, such as `slog3-sgamut3cine` or `rec709`.
A file's metadata can't tell, so send it only when you know. With it, exposure,
temperature, and tint run before any LUT, on the footage's own curve, which can
recover what the LUT would clip. It takes the same values as a LUT's
`inputColor`.

## When a transform fails

A request ZeroGen can refuse up front starts nothing:

| Response | When |
| - | - |
| `400 validation_failed` | The recipe doesn't fit the source, such as a frame past the end of the video. `param` names the field when ZeroGen can tell which one. |
| `403 permission_denied` | The key lacks a scope, or it's a [test key](https://docs.zerogen.com/developers/test-keys.md). |
| `404 not_found` | The asset, project, folder, or LUT isn't in your organization. `param` is `folderId` or `recipe.lut.lutId` for those two. |
| `429 rate_limited` | Your organization's keys are at their [running cap](https://docs.zerogen.com/developers/rate-limits.md#runs-in-progress). |

Some failures show only when the work runs. The run then ends as `failed`, with
the reason in its `error`: for example, despill, a LUT, and `adjust` refuse an
HDR source.
Convert the source to MP4 first, and transform that copy.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.