---
title: Presets
url: https://base_url.placeholder/docs/api/presets
group: Surfaces
---

# Presets

Presets: named, versioned lists of steps — atomic ops and processing-registry steps — saved once and run by slug, id or slug@version

7 endpoints under `/api/v1/presets`. What every call shares is on [the REST overview](https://base_url.placeholder/docs/api#conventions), and what they are for — with the calls for every SDK, the CLI and MCP — on [Presets](https://base_url.placeholder/docs/presets). `*` marks a required field.

- GET /api/v1/presets — List presets
- POST /api/v1/presets — Create a preset
- POST /api/v1/presets/import — Import presets
- GET /api/v1/presets/{slug} — Get a preset, or one earlier version of it
- PUT /api/v1/presets/{slug} — Update a preset — a new version when its steps or subjects change
- DELETE /api/v1/presets/{slug} — Delete a preset
- DELETE /api/v1/presets/{slug}/versions/{version} — Delete one superseded version

## List presets

GET /api/v1/presets

Built-in presets first, then your own, most recently updated first. Each entry is the current version — what the preset runs now — and `usage`, how much it is being run.

NOT PAGED: the built-ins and every preset of yours (an account holds at most 1000) come back in one answer, with no `meta`.

NO HISTORY BY DEFAULT: `versions` is left off, and `versionCount` says how many there are (the current one plus every superseded one). Read one preset's snapshots with GET /api/v1/presets/{slug}, or pass `?includeVersions=true` here for the export shape of every preset at once — which is what POST /api/v1/presets/import takes back.

USE THIS WHEN:

- Choosing a preset to run, or listing what an account has saved

DO NOT USE WHEN:

- You know the slug → GET /api/v1/presets/{slug}

FILTER: omit for both, `builtin` for the shipped ones only, `user` for your own only.

### Parameters

| Name | In | Type | Description |
| --- | --- | --- | --- |
| filter | query | string | Filter by source: 'builtin' or 'user'. Omit to return all.<br>one of builtin · user |
| includeVersions | query | boolean | Include each preset's superseded `versions` — the export shape. Off by default: a list row is read to choose a preset, and its history is up to 50 snapshots per preset. |

### Returns `200` — `data` is `PresetData[]`

Every preset the filter selects, built-ins first

### Errors

| Status | Code and when |
| --- | --- |
| 400 | `invalid_param` — `filter` is neither `builtin` nor `user` |

## Create a preset

POST /api/v1/presets

Accepts an [`Idempotency-Key`](https://base_url.placeholder/docs/errors#idempotency)

Saves version 1 and answers `201` with the preset as saved. Each step is `{op, model?, prompt?, parameters?}` — an op from GET /api/v1/ops — or `{operation, params}` — a processing-registry key, for what no op covers. Steps run in order, and they are checked when written, so a preset that saves is one a job can run (the 400s below name the step).

ANSWERS WITH `warnings` (advisory, the preset is saved): what the steps say that is legal and probably not meant — a format a later step shadows, a step the one before it already did.

NOT REFUSED: an AI step beside other steps, or several AI steps. Such a preset runs as one `chain` job, each item walking its segments in order (POST /api/v1/jobs).

LIMITS: at most 1000 presets per account, and 50 versions on record per preset (both `422 resource_limit_exceeded`); built-ins do not count.

SLUG: generated from the name when absent (a numeric suffix keeps it unique); one you send must be free, lowercase letters, digits and hyphens, and not start with `builtin-`.

CONSISTENCY (subjects): each subject is `{name, referenceAssetIds, descriptor}`. The images ride along on the generate / edit step and pin the geometry; the descriptor expands into its prompt wherever you write `{{subject.<name>}}` (/docs/presets). At most 4 subjects and 4 images across all of them, each your own and `DONE`; a subject with no images is refused. A name is a handle (`[a-z0-9][a-z0-9_-]{0,39}`, unique in the preset), a descriptor at most 300 characters.

### Request body — `PresetData`

### Returns `201` — `data` is `PresetData`

The preset as saved, with `warnings` when there is something to say

### Errors

| Status | Code and when |
| --- | --- |
| 400 | - `invalid_param` on `name` — missing or blank<br>- `invalid_param` on `steps` — no steps<br>- `invalid_param` on `steps[i]` — a step with both or neither of `op` / `operation`<br>- `invalid_param` on `steps[i].op` — an unknown op, or one that is not a step (`read_metadata`, `render_template`)<br>- `invalid_param` on `steps[i].op` — an op that answers with JSON (`analyze`) anywhere but last: nothing after it has an image<br>- `invalid_param` on `steps[i].op` — an op that takes no input image (`generate`) in a preset of more than one segment<br>- `invalid_param` on `steps[i].parameters.<name>` — a parameter outside the op's contract<br>- `invalid_param` on `steps[i].parameters.<name>` — `frame` / `metadata` / `density` given two values in one pass, or `frame=all` where the pass writes a still format<br>- `invalid_param` on `steps[i].model` — a model no provider runs or one that does not run the step's op; on a deterministic step, any model (`steps[i].prompt` likewise)<br>- `invalid_param` on `steps[i].operation` — an unknown registry operation; `steps[i].params…` — arguments it refuses<br>- `invalid_param` on `subjects` — see CONSISTENCY above: too many subjects or images, an image that is not your own `DONE` asset, a bad or repeated name, a long descriptor, or no generate / edit step to send them with<br>- `invalid_param` — the `slug` you sent is malformed, reserved or already one of yours |
| 422 | `resource_limit_exceeded` — the account already holds 1000 presets |

## Import presets

POST /api/v1/presets/import

Accepts an [`Idempotency-Key`](https://base_url.placeholder/docs/errors#idempotency)

Takes the list `GET /api/v1/presets?filter=user&includeVersions=true` returns — without `includeVersions` the rows carry no history — or any list of preset documents. `version` and `versions` are kept as exported (version defaults to 1), so an imported preset replays its history. Each entry is checked like a create; one that fails is reported in `errors` and the rest still land, so a `201` can carry refusals — read `errors`. A slug already in use gets a numeric suffix instead of being refused.

LIMITS: the whole import is refused (`422 resource_limit_exceeded`) if it would take the account past 1000 presets; an entry carrying more than 50 versions is reported in `errors` and the rest still land.

### Request body — `PresetData[]`

### Returns `201` — `data` is `BatchImportResultDTOPresetData`

What landed, and one line per entry that did not

### Errors

| Status | Code and when |
| --- | --- |
| 400 | `invalid_param` — the body is not a non-empty list |
| 422 | `resource_limit_exceeded` — the entries would take the account past 1000 presets; nothing was imported |

## Get a preset, or one earlier version of it

GET /api/v1/presets/{slug}

`{slug}` is a slug or id for the current version, or `slug@version` for one version — the spelling `POST /api/v1/jobs {presetId}` and `POST /api/v1/images/transform?preset=` accept, so what a job ran can always be read back. `?version=N` is the same as `slug@N`; give the version one way, not both.

The response is the export shape: `steps`, `subjects`, `version` and the superseded `versions`, plus `usage` over every version. An earlier version comes back with its own steps, subjects and `updatedAt` (when that version was saved) and without history.

### Parameters

| Name | In | Type | Description |
| --- | --- | --- | --- |
| slug* | path | string | Preset slug or id, or slug@version |
| version | query | integer (int32) | Read an earlier version (1 … current); the same as `slug@N` |

### Returns `200` — `data` is `PresetData`

The preset, at the version asked for

### Errors

| Status | Code and when |
| --- | --- |
| 400 | - `invalid_param` on `slug` — the version (`@N` or `?version=N`) is not a positive number<br>- `invalid_param` on `version` — the version is given both as `@N` and as `?version=` |
| 404 | `preset_not_found` — no such preset, no such version of it (never saved, or deleted), or it is not yours |

## Update a preset — a new version when its steps or subjects change

PUT /api/v1/presets/{slug}

Accepts an [`Idempotency-Key`](https://base_url.placeholder/docs/errors#idempotency)

Send only what changes: the body is merged over the current preset, and a field it leaves out keeps its value (`{"name": "…"}` is a whole rename; the slug stays unless you send one). What you do send is checked exactly as on create; `""` empties the description. A change to `steps` or `subjects` moves the preset to `version + 1` and keeps the previous one readable and runnable as `slug@version`, so a job or automation that pinned a version keeps running exactly that. A `name`, `description` or `slug` edit is not a version. Sending the steps it already has changes nothing. Answers with the preset as saved, and `warnings` as a create does.

DO NOT USE WHEN:

- Changing a built-in (403) → GET it and POST its steps as your own

### Parameters

| Name | In | Type | Description |
| --- | --- | --- | --- |
| slug* | path | string | Preset slug or id |

### Request body — `PresetData`

### Returns `200` — `data` is `PresetData`

The preset as saved — at `version + 1` when its steps or subjects changed

### Errors

| Status | Code and when |
| --- | --- |
| 400 | - `invalid_param` on `slug` — the path names a version (`slug@N`): a version cannot be changed, the next one is saved instead<br>- `invalid_param` on `name`, `steps[i]…` or `subjects` — what you sent fails a create's check (see POST /api/v1/presets)<br>- `invalid_param` — a new `slug` that is malformed, reserved or already one of yours |
| 403 | `forbidden` — a built-in preset; GET it and POST its steps as your own |
| 404 | `preset_not_found` — no such preset, or it is not yours |
| 422 | `resource_limit_exceeded` on `steps` — the preset already keeps 50 versions; delete one nothing pins (DELETE /api/v1/presets/{slug}/versions/{version}) or save the steps as a new preset |

## Delete a preset

DELETE /api/v1/presets/{slug}

Accepts an [`Idempotency-Key`](https://base_url.placeholder/docs/errors#idempotency)

Deletes the preset and every version of it; every `slug@N` a caller holds answers `404 preset_not_found` from then on. A job that already ran keeps its own copy of what it ran. Built-ins are 403.

### Parameters

| Name | In | Type | Description |
| --- | --- | --- | --- |
| slug* | path | string | Preset slug or id |

### Returns `204` — no body

Deleted; no body

### Errors

| Status | Code and when |
| --- | --- |
| 400 | `invalid_param` on `slug` — the path names one version (`slug@N`); that is DELETE /api/v1/presets/{slug}/versions/{version} |
| 403 | `forbidden` — a built-in preset |
| 404 | `preset_not_found` — no such preset, or it is not yours |

## Delete one superseded version

DELETE /api/v1/presets/{slug}/versions/{version}

Accepts an [`Idempotency-Key`](https://base_url.placeholder/docs/errors#idempotency)

Drops `slug@version` from the preset's history. After it, that reference is `404 preset_not_found` for good — nothing else changes: the preset keeps running whatever version it ran, the numbering is untouched (the next version is always `current + 1`, so a deleted number is never reissued), and a job that ran this version keeps its own copy of what it ran.

USE THIS WHEN:

- A preset is at its version ceiling (422 resource_limit_exceeded on PUT) and you know which earlier versions nothing pins
- A stored version carries a prompt you want gone

DO NOT USE WHEN:

- Anything might still call `slug@version` — a pinned reference stops resolving, and that is the whole cost of this call

To move what an UNPINNED call runs, PUT the old steps as a new version instead.

### Parameters

| Name | In | Type | Description |
| --- | --- | --- | --- |
| slug* | path | string | Preset slug or id — without an @, the version is the path segment below |
| version* | path | integer (int32) | The superseded version to delete |

### Returns `204` — no body

Deleted; no body

### Errors

| Status | Code and when |
| --- | --- |
| 400 | - `invalid_param` on `version` — the current version (save the next one first, or delete the whole preset), or a number below 1<br>- `invalid_param` on `slug` — the slug carries an `@version` of its own |
| 403 | `forbidden` — any version of a built-in preset |
| 404 | `preset_not_found` — no such preset, or no such version on record (never saved, or already deleted) |

## Objects

Each described once; a linked type is another object on this page.

### BatchImportResultDTOPresetData

What an import did: the entries that landed, and one line for each that did not

| Field | Type | Description |
| --- | --- | --- |
| data | PresetData[] | The created resources, as saved, in the order sent |
| errors | string[] | One line per refused entry, naming its index and why (`Preset at index 2 'Hero': …`). Absent when every entry landed. |
| importedCount | integer (int32) | How many entries were created — the length of `data` |

### PresetData

A preset: a named, versioned list of steps, each an L1 op or a processing-registry step. GET returns the export shape; POST /api/v1/presets/import takes it back.

| Field | Type | Description |
| --- | --- | --- |
| builtIn | boolean | Whether this is a built-in preset (read-only) |
| createdAt | string (date-time) | When the preset was created |
| description | string | Free text for people; not run. `""` on a PUT empties it. |
| id | string | Preset id (`pre_…`; a built-in's starts with `builtin-`). A job's `presetId` takes it, the slug, or either with `@version`. |
| name | string | Display name (required on create).<br>e.g. "Product cut-out, 1200 WebP" |
| slug | string | URL-safe handle, unique per account: lowercase letters, digits and hyphens. Generated from the name when absent (a numeric suffix keeps it unique); one you send must be free. `builtin-` is reserved.<br>e.g. "product-cutout" |
| steps | PresetStep[] | The steps, run in order. Each is `{op, model?, prompt?, parameters?}` (an op from GET /api/v1/ops) or `{operation, params}` (a processing-registry key, for what no op covers). Checked when written: a parameter outside its op's contract is 400 invalid_param on `steps[i].parameters.<name>`. Adjacent deterministic steps run as one `process` pass and each AI step as its op's own job; those are the preset's segments. One segment runs as that job; more than one — an AI step beside other steps, or several AI steps — runs as one `chain` job, each item walking the segments in order with the output of one as the input of the next. |
| subjects | Subject[] | What has to look the same every time: each subject is `{name, referenceAssetIds, descriptor}`, at most 4 of them. The images (max 4 across all subjects, your own, status DONE) ride along on the preset's generate / edit step and pin the geometry; the descriptor expands into its prompt wherever you write `{{subject.<name>}}`. A preset with subjects needs a generate or edit step. |
| updatedAt | string (date-time) | When the preset was last saved, a rename included — the order your own rows are listed in. On an earlier version read as `slug@N`, when that version was saved. |
| usage | PresetUsage | How often this preset has run and when it last did, over the jobs still on record (imagestep#423). Present on reads — GET /api/v1/presets and GET /api/v1/presets/{slug} — and left off a write, which answers about what it just saved. Derived, never stored. |
| version | integer (int32) | Starts at 1 and goes up whenever `steps` or `subjects` change; a name, description or slug edit is not a version. Run or read an earlier one as `slug@version` / `?version=N`.<br>e.g. 3 |
| versionCount | integer (int32) | How many versions are on record: the current one plus every superseded one. Always present on a read, whether or not `versions` is, so a list reader can tell a preset with history from one without fetching it.<br>e.g. 3 |
| versions | PresetVersion[] | Superseded versions, oldest first. Present in exports so an import replays the same history. GET /api/v1/presets leaves it off unless you ask for `?includeVersions=true` — a list row does not need every snapshot of every preset, and `versionCount` says whether there is any history to read. |
| warnings | StepWarning[] | What these steps say that is legal, will run, and is probably not what was meant (imagestep#416): a format a later step shadows, a step the one before it already did. Advisory — the preset was saved. Derived, never stored: a write answers with the findings about what it just saved, and POST /api/v1/jobs?dryRun=true recomputes them for any preset. What could not run at all was refused instead, 400 invalid_param naming the step. |

### PresetStep

A preset step: either an L1 op from GET /api/v1/ops — {op, model?, prompt?, parameters?} — or a processing-registry step — {operation, params} — for what no op covers. Never both.

| Field | Type | Description |
| --- | --- | --- |
| model | string | AI steps: the model; the op's default when absent. |
| op | string | An op from GET /api/v1/ops (kind ai or deterministic).<br>e.g. "resize" |
| operation | string | A processing-registry key (deterministic only), for steps no op covers.<br>e.g. "sharpen" |
| parameters | object | The op's parameters. A deterministic op's are checked against its catalogue contract when the steps are written, and so are `analyze`'s bounds; an AI image op's go to the model as sent. |
| params | any | That registry step's arguments. |
| prompt | string | AI steps: the prompt. {{subject.<name>}} expands to that subject's descriptor. |

### PresetUsage

How often this preset has been run, over the jobs still on record — job records expire with the assets they produced, so this is the retention window and not all time. Read-only and derived.

| Field | Type | Description |
| --- | --- | --- |
| lastRunAt | string (date-time) | When the most recent of them was submitted.<br>e.g. "2026-09-21T09:14:22Z" |
| runs | integer (int64) | Jobs that ran this preset, at any version, within the retention window.<br>e.g. 12 |

### PresetVersion

A superseded version of a preset: its steps and subjects as they were, and when that version was saved.

| Field | Type | Description |
| --- | --- | --- |
| steps | PresetStep[] | The steps as that version had them. |
| subjects | Subject[] | The subjects as that version had them. |
| updatedAt | string (date-time) | When that version was saved. |
| version | integer (int32) | The version number; `slug@version` runs and reads exactly this. |

### StepWarning

An advisory finding about a list of steps: legal, and probably not what was meant

| Field | Type | Description |
| --- | --- | --- |
| code | string | Machine-readable finding code from a closed set; the table is /docs/presets#warnings<br>e.g. "format_shadowed" |
| message | string | The finding in one sentence, naming the steps by index<br>e.g. "steps[1] writes jpeg and steps[3] writes webp; a process segment writes one image, in the format of its last format step, so steps[1]'s format is not written" |
| steps | integer (int32)[] | The step indexes the finding is about, in order — the redundant one first<br>e.g. [1,3] |

### Subject

A recurring character, product or location: reference images plus the locked words for it.

| Field | Type | Description |
| --- | --- | --- |
| descriptor | string | The locked description — colour, material, markings — at most 300 characters. Text pins what an image cannot show. |
| name | string | Lowercase handle (`[a-z0-9][a-z0-9_-]{0,39}`, unique among the subjects), referenced in a prompt as {{subject.<name>}}.<br>e.g. "hero" |
| referenceAssetIds | string[] | Your own DONE asset ids showing this subject. They pin the geometry. |
