---
title: "Update a preset — a new version when its steps or subjects change"
method: PUT
path: "/api/v1/presets/{slug}"
tags: ["Preset Management"]
---

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

`PUT /api/v1/presets/{slug}`

## Request

- Base URL: `https://api.imagestep.dev`
- URL: `https://api.imagestep.dev/api/v1/presets/{slug}`
- Auth: API key in header `Authorization`

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

## Path parameters

- `slug` string, required

## Headers

- `Idempotency-Key` string

## Request body

- 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.
  - `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).
  - `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.
  - `description` string: Free text for people; not run. `""` on a PUT empties it.
  - `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.
    - `name` string: Lowercase handle (`[a-z0-9][a-z0-9_-]{0,39}`, unique among the subjects), referenced in a prompt as {{subject.<name>}}.
    - `referenceAssetIds` string[]: Your own DONE asset ids showing this subject. They pin the geometry.
    - `descriptor` string: The locked description — colour, material, markings — at most 300 characters. Text pins what an image cannot show.
  - `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.
    - `op` string: An op from GET /api/v1/ops (kind ai or deterministic).
    - `model` string: AI steps: the model; the op's default when absent.
    - `prompt` string: AI steps: the prompt. {{subject.<name>}} expands to that subject's descriptor.
    - `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.
    - `operation` string: A processing-registry key (deterministic only), for steps no op covers.
    - `params` object: That registry step's arguments.
  - `version` integer: 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`.
  - `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.
    - `version` integer: The version number; `slug@version` runs and reads exactly this.
    - `steps` PresetStep[]: The steps as that version had them.
      - `op` string: An op from GET /api/v1/ops (kind ai or deterministic).
      - `model` string: AI steps: the model; the op's default when absent.
      - `prompt` string: AI steps: the prompt. {{subject.<name>}} expands to that subject's descriptor.
      - `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.
      - `operation` string: A processing-registry key (deterministic only), for steps no op covers.
      - `params` object: That registry step's arguments.
    - `subjects` Subject[]: The subjects as that version had them.
      - `name` string: Lowercase handle (`[a-z0-9][a-z0-9_-]{0,39}`, unique among the subjects), referenced in a prompt as {{subject.<name>}}.
      - `referenceAssetIds` string[]: Your own DONE asset ids showing this subject. They pin the geometry.
      - `descriptor` string: The locked description — colour, material, markings — at most 300 characters. Text pins what an image cannot show.
    - `updatedAt` string, date-time: When that version was saved.
  - `versionCount` integer: 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.
  - `createdAt` string, date-time: When the preset was created
  - `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.
  - `builtIn` boolean: Whether this is a built-in preset (read-only)
  - `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.
    - `code` string: Machine-readable finding code from a closed set; the table is /docs/presets#warnings
    - `message` string: The finding in one sentence, naming the steps by index
    - `steps` integer[]: The step indexes the finding is about, in order — the redundant one first
  - `usage` 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.
    - `runs` integer: Jobs that ran this preset, at any version, within the retention window.
    - `lastRunAt` string, date-time: When the most recent of them was submitted.

## Response `200`

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

## Other responses

- `400`: - `invalid_param` on `slug` — the path names a version (`slug@N`): a version cannot be changed, the next one is saved instead - `invalid_param` on `name`, `steps[i]…` or `subjects` — what you sent fails a create's check (see POST /api/v1/presets) - `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
- `409`: Conflict — `error.code` is `idempotency_key_reuse` (the key already means a different request) or `request_in_progress` (the first request under this key has not settled yet; retryable).
- `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
- `429`: Rate limited — `error.code` is `rate_limited` and `retryable` is true. `Retry-After` says how many seconds to wait.

---

[API](https://skmtc.dev/imagestep/apis/imagestep-api.md) · [All operations](https://skmtc.dev/imagestep/apis/imagestep-api/llms.txt) · [OpenAPI document](https://skmtc.dev/imagestep/apis/imagestep-api/revisions/5e8e922a3085?raw)
