CLI
imagestep is the terminal face of the same API every SDK uses: transform a local file and get the bytes back, upload a directory, price a batch before it runs, submit a job and wait for it, publish the results. Every command that prints a resource takes -o json, and the exit code says whether a failure is worth retrying — so a shell script is a complete automation, and a coding agent can drive it without reading prose. The top of this page is how the CLI behaves; below it, every command.
pnpm add -g imagestep-cli # or: npm install -g imagestep-cli
imagestep login # browser sign-in; a key is issued to this machine
imagestep image resize ./photo.jpg --width 1200 --out small.jpg # local in, local out, nothing stored
imagestep asset upload ./shoot/*.jpg -c shoot-01 # upload into the collection "shoot-01"
imagestep jobs submit --op remove_bg --asset-ids <id> --wait # an AI op is a job; --wait blocks until it is doneInstall
npm package imagestep-cli, binary imagestep, Node.js 22.19 or newer. Install it globally, or run it without installing.
pnpm add -g imagestep-cli # npm install -g imagestep-cli
pnpm dlx imagestep-cli --version # or: npx imagestep-cli --version — no install
imagestep --help # every group; imagestep <group> --help lists its commands and flagsA bare imagestep prints the command list and exits 0. --version is the number to quote when you report a problem with the CLI itself.
Sign in, or give it a key
Two ways in. A person at a terminal signs in once; a script, a CI runner or an agent gets a key in the environment and never opens a browser.
At a terminal: login
imagestep login # opens the browser; reuses this machine's stored key unless the service rejects it
imagestep login --force # always issue a new key
imagestep logout # revokes this machine's key on the server, then forgets itimagestep login— listens on 127.0.0.1, port 3456–3465/cli-auth— opens in your browser, signed in; you press one buttoncallback— the browser hands the listener a one-time codePOST /api/v1/cli-auth/token— the code plus a secret that never left the process — the key comes back~/.imagestep/config.yml— the only place the key is written
login starts a listener on 127.0.0.1:3456 (or the next free port up to 3465), opens https://imagestep.dev/cli-auth (and prints the URL if no browser can be opened), and waits up to five minutes for you to press one button. The browser hands the CLI a one-time code, which the CLI trades for the key with a secret it never let out of the process — so the key is never in a URL your browser remembers. It lands in ~/.imagestep/config.yml, readable by you alone, and nowhere else. When a key is already stored it is probed once and reused unless the service rejects it, so login is safe to run again; --force skips the reuse. logout revokes first and clears second, in that order: if the revoke fails, the command says the key is still valid and points you at /keys rather than printing a reassuring "logged out" over a key that still works.
In a script, a container or an agent: a key in the environment
export IMAGESTEP_API_KEY=is_sk_… # created on /keys
imagestep jobs list # works on a machine with no ~/.imagestep at allIMAGESTEP_API_KEY wins over the config file, is read on every call, and is never written to disk — the same variable the Python SDK and the MCP server read. With it exported, login says so and exits 0 without opening a browser, and logout does not revoke it — that key was not issued to this machine, and revoking it would break every other runner holding it. IMAGESTEP_BASE_URL points the CLI at another API host (a local bring-up); with it unset every call goes to https://api.imagestep.dev.
Output, streams and paging
Every command that prints a resource takes -o, --output: json, yaml or table, and for a job (jobs get, jobs wait) pretty instead of table. The default differs per command — a table for lists, JSON for most single documents and writes, pretty for a job, Markdown for guidelines — and the reference below shows each one, so a script always passes the one it wants. A command that only writes a file or removes something — asset download, the image transforms and image render, preset, template and webhook delete — prints no document and takes no -o.
With -o json, stdout is one JSON document — the answer, or the error — and everything else (progress, "Job submitted", counts, log lines) is on stderr. The one exception is image metadata with several files: one document per file. Keep the streams apart: pipe stdout into jq, never 2>&1 into a parser.
ID=$(imagestep asset upload ./product.jpg -c shoot-01 -o json | jq -r '.[0].assetId')
JOB=$(imagestep jobs submit --op remove_bg --asset-ids "$ID" --wait -o json | jq -r '.id')
OUT=$(imagestep jobs outputs "$JOB" -o json | jq -r '.[0].assetId')
imagestep asset publish "$OUT" -o json | jq -r '.[0].publicUrl'A list is one page of up to 100 rows (-s, --per-page). When there are more, the command says so on stderr — More: --cursor <cursor> — and --cursor reads the page after; --all walks every page for you and prints them as one array. That holds for asset list, asset collections, jobs list, jobs items, webhook deliveries and feedback list; template list and jobs outputs always read every page.
imagestep asset list --collection shoot-01 --all -o json | jq -r '.[].id' # the whole collection, one arrayExit codes, errors and retries
The exit code carries the branch a script takes. The service's own codes start at 3 because jobs wait and --wait keep 2 for their timeout.
| Exit | Meaning | What to do |
|---|---|---|
| 0 | Done. For jobs wait / --wait: the job COMPLETED. | |
| 1 | A local failure (usage, a missing file, an unknown command, a destructive command without -y), a partially failed upload, or a job that FAILED or was CANCELLED. | Read stderr — or, for a job, the job document on stdout: errorMessage, items[].error, actualCredits (a failed item is not charged). |
| 2 | jobs wait / --wait ran out of time. The job is still running; nothing was cancelled. | Wait again: imagestep jobs wait <id>. |
| 3 | The service refused this request and retryable is false. | Fix what param names; do not resend the same body. param: null (for example insufficient_credit) means the account, not the request, is the problem. |
| 4 | The service failed transiently and retryable is true (for an answer without the error body: a 429 or a 5xx) — or never answered: a network failure or a timeout, once the retries are spent. | Wait, then run the same command again. |
At a terminal an API error prints as <status> <code>: <message> with the offending parameter, a line saying whether retrying may succeed, and the request id to quote when you report it. With -o json the same error is JSON on stdout, in the API's own shape, so one parser reads both the answer and the refusal:
{"error":{"code":"insufficient_credit","message":"Insufficient credits","retryable":false,"param":null,"requestId":"…","status":402}}Branch on retryable, not on the message. code is a closed set, param names the field to fix, and details carries the sub-reason. Every call already retries a transient answer twice more, after its Retry-After — a write with the same Idempotency-Key, the image group included — so exit 4 means the retries are spent: wait longer before the next try. A refusal is never retried; insufficient_credit carries details.topUpUrl, the page that clears it.
Three things the table does not show. Each attempt gets 30 seconds (120 in the image group, which uploads the file), and a network failure or a timeout that outlives the retries is exit 4 like any transient failure: with -o json its error has code: null, retryable: true and no status — nothing answered. In the image group each failed file is reported on stderr and the run exits with the lowest code among them: 4 only when every failure was transient, so running it again is the whole fix; 3 when the service refused one. And jobs outputs --download exits 1 when one download fails, having saved the rest.
Writing a script
Everything above composes into a shell script that is a complete automation: price, run, publish, stop on the right error. The example uploads a product photo, prices a two-step chain — remove the background, then upscale the cut-out — runs it as one job and prints one URL.
#!/usr/bin/env bash
set -euo pipefail
export IMAGESTEP_API_KEY="${IMAGESTEP_API_KEY:?set a key from https://imagestep.dev/keys}"
STEPS='[{"op":"remove_bg"},{"op":"upscale","parameters":{"scaleFactor":2}}]'
ID=$(imagestep asset upload "$1" -c "product-shots" -o json | jq -r '.[0].assetId')
# price every step before the first one runs; jq -e fails the script when the balance is short
imagestep jobs estimate --steps "$STEPS" --asset-ids "$ID" -o json | jq -e '.sufficientCredit' > /dev/null
JOB=$(imagestep jobs submit --steps "$STEPS" --asset-ids "$ID" --wait -o json | jq -r '.id')
OUT=$(imagestep jobs outputs "$JOB" -o json | jq -r '.[0].assetId')
imagestep asset publish "$OUT" -o json | jq -r '.[0].publicUrl'With set -e, exit 3 stops the script at the refused step and exit 4 stops it at the transient one; wrap the submit in a loop that retries on 4 if the run must survive a provider blip. A batch that should not care about one bad file uses the image group with --dir, which carries on past a failure and reports it in the exit code at the end.
Using it from a coding agent
Claude Code, Codex, Cursor or any agent with a shell drives this CLI the way a script does, and ImageStep publishes the operating procedure for it as a skill: read the catalogue instead of guessing, price before spending, carry asset ids instead of bytes, branch on the exit code, and stop when the account rather than the request is the problem. One command installs it:
imagestep skill --install claude-codeThe skill, the install paths and what the agent does with it are on the agent skill page. An agent without a shell — Claude Desktop, a hosted agent — uses the MCP server instead; both reach the same API with the same key.
image — local files in, local files out, nothing stored
The group for the case where you are holding an image and only want the result back. The bytes go up, the result comes down, and your account is exactly as it was: no upload, no asset, no job, no public URL. It is the terminal face of the synchronous endpoints: a transform or a render counts one against your plan's processing allowance — free on a paid plan, paid from the balance past it on Free — and metadata is free.
imagestep image resize ./in.jpg --width 1200 --out ./out.jpg # one file → one file
imagestep image compress ./photos/*.jpg --dir ./out --quality 75 # many files → one directory
imagestep image convert ./in.png --format webp --quality 80 --out ./out.webp
imagestep image run --preset builtin-util-web-optimize ./in.jpg --out ./out.webp # a deterministic preset, several steps in one call
imagestep image render --template builtin-template-og-image --data '{"title":"Hello"}' --out ./og.png
imagestep image metadata ./photo.dng -o table # EXIF, GPS, dimensions, SHA-1 — freeThe op subcommands are generated from the catalogue. Every op whose syncEndpoint in GET /api/v1/ops is the transform endpoint becomes a subcommand, and its parameter contract becomes flags — --width 1200 exists because the API says resize takes width. Run imagestep image --help to see today's list (it needs no key) and imagestep image <op> --help for that op's flags. AI ops are not here at all: anything with a model in it is a job, because a job is what owns the retry, the settlement and the cancellation a provider call needs.
| Command | What it does | Flags |
|---|---|---|
| image <op> <files...> | Run one deterministic op on local files | generated from the catalogue when the CLI runs: the op's parameters are its flags, plus the image run flags below |
| image metadata <files...> | Read EXIF, GPS, dimensions and SHA-1 — free, and nothing is storedprints each file's JSON — one document per file — so no --out or --dir; --concurrency files at a time, retried like the rest, one bad file fails alone | -o, --output <format> (default json)--concurrency <n> (default 4)--no-retry |
| image run <files...> | Run a saved deterministic preset over local filesseveral steps in one call; the preset must contain no AI step | --preset <idOrSlug> (required)--out <file>-d, --dir <directory>--concurrency <n> (default 4)--no-retry |
| image render | Render one row of data through a template into a PNG — nothing is stored (many rows: jobs submit --op render_template --items) | --template <id> (required)--data <json> (required)--out <file> (required)--no-retry |
Flags the whole group shares — as image run declares them
| Flag | Default | What it does |
|---|---|---|
| --out <file> | Write the result here (single input only)single input only; written verbatim — resize does not change the format, convert does; the only way to write over the input | |
| -d, --dir <directory> | Write results into this directory (one file per input)mutually exclusive with --out; the extension follows the response Content-Type | |
| --concurrency <n> | 4 | How many files to process at once |
| --no-retry | Do not retry retryable failures (429 / 503)without it, a retryable answer (a 429, a 503) is sent twice more, after its Retry-After; an insufficient_credit refusal also stops the files not yet sent |
-o still means the output format here, as in every other command; the output file is --out, so a flag never has two meanings. With neither --out nor --dir, each result is written to the current directory under its input's name, with the extension the result's type calls for. A result never replaces its input unless --out names it: one that would is not written, that file fails, and the files not yet sent are not sent. The input's type is detected from its bytes (then from the extension for RAW and SVG, which have no signature); a file nothing can name is refused before any bytes are sent. With several inputs, one bad file fails alone and the rest carry on.
asset — upload, search, publish
Everything outside the image group works on assets: an image stored in your account with a stable id. Upload from disk or hand the service a URL to fetch; put uploads in a collection so you can find them again; publish the ones you want a permanent URL for.
imagestep asset upload ./shoot/*.jpg -c shoot-01 -o json # wildcards ok; a directory's relative path joins the collection name
imagestep asset from-url https://example.com/a.jpg -c inbox # the service fetches it; batches of 20
imagestep asset list --collection shoot-01 --min-width 2000 -o table # every filter is optional and they stack
imagestep asset get <asset-id>
imagestep asset download <asset-id> --variant original -f ./original.heic
imagestep asset publish <asset-id> <asset-id> -o json # → publicUrl on the CDN; --off to unpublish
imagestep asset set-collection <asset-id> -c archive-2026 # put it in another collection
imagestep asset tag <asset-id> --tags hero,spring-sale # replace the tags; list --tag hero finds it
imagestep asset delete <asset-id> -y # permanent — there is no trash| Command | What it does | Flags |
|---|---|---|
| asset upload <file-paths...> | Upload assets from local paths (wildcards ok; a directory's relative path becomes part of the collection name)hidden files are skipped, and the same SHA-1 is deduplicated by the server | -c, --collection <name>--tags <list>--retention-days <n>--concurrency <number> (default 3)-m, --mime-type <types...>-o, --output <format> (default table) |
| asset from-url <urls...> | Create assets from public image URLs, fetched by the service (batches of 20; any failed URL exits 1)never fetched by your machine; each URL succeeds or fails on its own | -c, --collection <name>--tags <list>--retention-days <n>-o, --output <format> (default table) |
| asset list | List and search assets — every filter is optional and they compose | -v, --view <view> (default all)-c, --collection <name>--tag <tag>--mime <type>--source <op>--min-width <px>--max-width <px>--min-height <px>--max-height <px>--taken-from <when>--taken-to <when>--created-from <when>--created-to <when>--status <state>--has-collection <bool>--job-id <id>--op <op>-q, --query <text>-p, --page <page> (default 0)-s, --per-page <perPage> (default 100)-a, --all--cursor <cursor>-o, --output <format> (default table) |
| asset collections | List your collections, most recently added to first, with how many assets each holds | -q, --query <text>-p, --page <page> (default 0)-s, --per-page <perPage> (default 100)-a, --all--cursor <cursor>-o, --output <format> (default table) |
| asset rename-collection <from> <to> | Move every asset in one collection to another name (a rename or a merge; "" takes them out of any collection)one request; prints { from, to, updated } | -o, --output <format> (default json) |
| asset get <asset-id> | Get asset file details by ID | -o, --output <format> (default json) |
| asset download <asset-id> | Download an asset's private bytes (follows the signed redirect; the API key never reaches storage) | --variant <variant> (default readable)-f, --file <path> |
| asset delete <asset-ids...> | Delete assets permanently (irreversible: there is no trash and no restore)without -y nothing is deleted and it exits 1, at a terminal too — there is no prompt | -y, --yes-o, --output <format> (default json) |
| asset set-collection <asset-ids...> | Put assets in a collection (one batch request)there is no folder tree: a collection is an opaque name | -c, --collection <name> (required)-o, --output <format> (default json) |
| asset tag <asset-ids...> | Replace the tags on assets (one batch request; `asset list --tag` finds them again) | --tags <list> (required)-o, --output <format> (default json) |
| asset publish <asset-ids...> | Publish assets (use --off to unpublish)prints the asset documents — read .publicUrl | --off-o, --output <format> (default json) |
-c, --collection names a collection, not a folder: an opaque string attached to each asset, filtered later with asset list --collection. asset upload -o json prints only the per-file results on stdout, one object per file with fileName, status, assetId, collection and size; progress goes to stderr, and a file that failed makes the run exit 1.
jobs — price, submit, wait, outputs
A job is one op (or one preset) over one or more assets: per-item progress, per-item failure, one settlement. Every AI op is a job, and so is any deterministic op whose result you want kept as an asset. The four commands you will use most, in the order you use them:
# 1. price the exact body you are about to submit — nothing is created
imagestep jobs estimate --op upscale --asset-ids <id>,<id> --params '{"scaleFactor":2}' -o json
# 2. submit it and block until it finishes (exit 0 done · 1 failed / cancelled · 2 still running at --timeout)
imagestep jobs submit --op upscale --asset-ids <id>,<id> --params '{"scaleFactor":2}' --wait -o json
# 3. every result asset, in item order; --download saves the bytes
imagestep jobs outputs <job-id> --download ./out
# 4. give the results public URLs
imagestep asset publish <asset-id> <asset-id> -o json| Command | What it does | Flags |
|---|---|---|
| jobs list | List jobs | -p, --page <n> (default 0)-s, --per-page <n> (default 100)--status <status>--type <type>--preset <ref>--op <op>--root-job-id <id>--created-from <when>--created-to <when>-a, --all--cursor <cursor>-o, --output <format> (default table) |
| jobs get <id> | Get job details (including per-item results) | -o, --output <format> (default pretty) |
| jobs estimate | Estimate credit cost for a job without submittingthe same flags as submit; returns estimatedCredits, creditBalance, sufficientCredit (and processCountLeft for a deterministic op) | --op <op>--type <type>--asset-ids <ids>--count <n>--preset-id <ref>--steps <json>--subjects <json>-m, --model <model>-p, --prompt <prompt>--params <json>-c, --collection <name>--retention-days <n>--preset <json>--variants <json>--template-id <ref>--items <json>--image-count <n>-o, --output <format> (default json) |
| jobs submit | Submit a new async job (ai-generate | ai-edit | parse | process | render | chain)--wait blocks until it is terminal (exit 0 done · 1 failed or cancelled · 2 still running at --timeout); sends an Idempotency-Key and retries a transient answer with the same key, so a retry never becomes a second job | --op <op>--type <type>--asset-ids <ids>--count <n>--preset-id <ref>--steps <json>--subjects <json>-m, --model <model>-p, --prompt <prompt>--params <json>-c, --collection <name>--retention-days <n>--preset <json>--variants <json>--template-id <ref>--items <json>--timeout <seconds>--wait-o, --output <format> (default json) |
| jobs wait <id> | Wait for a job to finish: exit 0 COMPLETED, 1 FAILED / CANCELLED, 2 timeoutthe service does the waiting (GET /jobs/{id}?wait=, answered the moment the job settles), one progress line on stderr; nothing is cancelled at the timeout | --timeout <seconds>-o, --output <format> (default pretty) |
| jobs items <id> | List a job's items — what each one did, and the index the rest of the API names it by | -p, --page <n> (default 0)-s, --per-page <n> (default 100)--status <status>-a, --all--cursor <cursor>-o, --output <format> (default table) |
| jobs outputs <id> | List what a job produced, in item order: its assets (optionally downloaded) and analyze answersthe items that produced something — a result asset, or an analyze answer (output); --download saves each asset through the signed redirect, --concurrency at a time, streamed to disk and given up on (then tried once more) when storage stalls | --download <dir>--concurrency <n> (default 4)-o, --output <format> (default table) |
| jobs resume <id> | Resume incomplete items from a failed/cancelled job (creates a new attempt linked via rootJobId)a new attempt with its own id; priced like the original | -o, --output <format> (default json) |
| jobs cancel <id> | Cancel a pending or running jobitems already sent to a provider finish and are charged | -o, --output <format> (default json) |
Describing the work: the submit flags
--op is the vocabulary: any op name from imagestep ops list. The service derives the job type from it, so --type is only for the rare call that names neither an op nor a preset. Every JSON flag takes a string or a path to a file, and the service validates it — a bad value is a 400 invalid_param naming the field, never a silent default.
| Flag | Default | What it does |
|---|---|---|
| --op <op> | Atomic op from GET /api/v1/ops (resize, remove_bg, generate, …); the job type follows from itfrom the catalogue; decides the job type | |
| --type <type> | Job type when neither --op nor --preset-id decides it (ai-generate|ai-edit|parse|process|render|chain); default ai-generate | |
| --asset-ids <ids> | Comma-separated asset IDs | |
| --count <n> | Number of images (text-to-image jobs) | |
| --preset-id <ref> | A saved preset: slug, id, or slug@version to pin one version (it decides the job type)@version pins one version; the preset decides the job type | |
| --steps <json> | An inline chain: the steps a preset would store, [{op, model?, prompt?, parameters?} | {operation, params}], run once without saving one (JSON string or file path) | |
| --subjects <json> | With --steps: the subjects a preset stores, [{name, referenceAssetIds, descriptor}] — reference images and locked words for its generate / edit steps (JSON string or file path) | |
| -m, --model <model> | AI model ID (overrides preset)ids and prices from imagestep models list | |
| -p, --prompt <prompt> | Prompt text (string, or @path to file) — overrides preset | |
| --params <json> | Model parameters (JSON string or file path)keys are the op's params entries — {"width":1200} for resize, {"scaleFactor":2} for upscale | |
| -c, --collection <name> | Collection to put the output assets in (default: the input's) | |
| --retention-days <n> | Keep the outputs (and the job's record) this many days instead of your plan's retention — shorter only | |
| --preset <json> | Inline processing pipeline {name, pipeline} (JSON string or file path, process jobs only) | |
| --variants <json> | One output per entry: [{name, parameters}] merged over --params — a whole set of sizes in one job (≤ 20, deterministic ops; JSON string or file path)every social size from one submit | |
| --template-id <ref> | render_template: the template id, or id@version to pin onethe template is the input: there are no asset ids | |
| --items <json> | render_template: one object of template variables per image (≤ 500; JSON string or file path) | |
| --timeout <seconds> | How long to wait before giving up (exit 2), default 600 | |
| --wait | Wait for the job to finish: exit 0 COMPLETED, 1 FAILED / CANCELLED, 2 timeout |
imagestep jobs submit --op generate --count 2 -p "a red bicycle on white" --wait -o json
imagestep jobs submit --asset-ids <id>,<id> --preset-id builtin-util-web-optimize@1 --wait
# three social sizes from each photo, one job (2 assets × 3 variants = 6 items)
imagestep jobs submit --op resize --asset-ids <id>,<id> --params '{"fit":"cover"}' \
--variants '[{"name":"ig","parameters":{"width":1080,"height":1350}},{"name":"og","parameters":{"width":1200,"height":630}},{"name":"x","parameters":{"width":1600,"height":900}}]'
# one OG image per row of a file
imagestep jobs submit --op render_template --template-id builtin-template-og-image --items ./rows.json --waitChaining
Two ops in a row — remove the background, then resize — are one job: jobs submit --steps <json|file> with the steps inline, as the script above does, or preset create once and jobs submit --preset-id <slug> for a chain you will run again — a preset is the same steps with a name and a version. One price, one settlement, one thing to wait on, and the images in between are cleaned up for you. Pin a preset with @version and a batch run months apart is the same batch. Two separate jobs joined by an asset id — the assetId that jobs outputs prints for the first is the --asset-ids of the second — is for the case where what you run next depends on what the first one produced.
preset and template
A preset is a saved, versioned list of steps; changing its steps makes a new version, and slug@version pins one. A template is the HTML/CSS input of render_template, versioned the same way. Built-ins (builtin-…) are read-only: start from one by getting it and creating your own from its body.
imagestep preset list -f user -o table
imagestep preset get web-optimize@2 -o json
imagestep preset create -f ./instagram-square.json # a recipe off /docs/recipes, as it is printed
imagestep preset create -n "web-optimize" -s '[{"op":"resize","parameters":{"width":1600}},{"op":"convert","parameters":{"format":"webp","quality":80}}]'
imagestep preset update web-optimize -s ./steps.json # new steps → a new version
imagestep preset import ./presets.json
imagestep template list
imagestep template get builtin-template-og-image -o json
imagestep template create --name price-card --html @card.html --css @card.css --width 1200 --height 630
imagestep template update price-card --css @card-v2.css # a new version; everything you leave out carries over
imagestep template versions price-card| Command | What it does | Flags |
|---|---|---|
| preset list | List all presets — `-o json` and `-o yaml` print the export shape, history included | -f, --filter <filter>-o, --output <format> (default table) |
| preset get <preset-slug> | Get a preset by slug, or one earlier version of it as slug@version | -o, --output <format> (default json) |
| preset create | Create a preset (version 1) from a JSON document — a /docs/recipes block, or what `preset get -o json` prints — and/or flags-f drops what the service assigns — the id, the version, a builtin- slug | -f, --file <json>-n, --name <name>-s, --steps <json>--slug <slug>--description <text>-o, --output <format> (default json) |
| preset update <preset-slug> | Update a preset — changing its steps makes a new version; what the flags (or -f) leave out is carried overthe service merges what you pass over the current version | -f, --file <json>-n, --name <name>-s, --steps <json>--slug <slug>--description <text>-o, --output <format> (default json) |
| preset delete <preset-slug> | Delete a preset | -y, --yes |
| preset delete-version <preset-slug> <version> | Delete one superseded version — slug@version stops resolving for good; the way past the version ceiling | -y, --yes |
| preset import <json> | Import a list of presets — the export shape, which is what `preset list -o json` prints | -o, --output <format> (default json) |
| template list | List templates: built-ins first, then your own (-o json|yaml prints full documents, the import format) | -f, --filter <filter>-o, --output <format> (default table) |
| template get <id> | Get a template, or one version of it as id@version | -o, --output <format> (default json) |
| template versions <id> | Every saved version of a template, newest first | -o, --output <format> (default table) |
| template create | Create a template (version 1) from a JSON document — the shape `template get -o json` prints — and/or flags | -f, --file <json>--name <name>--description <text>--html <html>--css <css>--width <px>--height <px>--variables <names>--slug <slug>-o, --output <format> (default json) |
| template update <id> | Save a new version — what the flags (or -f) leave out is carried over from the current version | -f, --file <json>--name <name>--description <text>--html <html>--css <css>--width <px>--height <px>--variables <names>--slug <slug>-o, --output <format> (default json) |
| template delete <id> | Delete a template and every version of it (built-ins are 403) | -y, --yes |
| template import <file> | Import templates from a JSON file (the list `template list -f user -o json` prints); each becomes version 1 | -o, --output <format> (default json) |
webhook
Register an https URL and stop polling: the service POSTs signed job events to it. The signing secret is printed exactly once, by create and rotate-secret; every other command masks it. What the events look like and how to verify a signature is on webhooks.
imagestep webhook create --url https://example.com/hooks/imagestep --events job.completed,job.failed
imagestep webhook test <endpoint-id> # a synthetic webhook.test now; exit 1 unless DELIVERED
imagestep webhook deliveries <endpoint-id> # attempts, receiver status, error, next retry
imagestep webhook update <endpoint-id> --enable # the way back after an automatic disable
imagestep webhook rotate-secret <endpoint-id>| Command | What it does | Flags |
|---|---|---|
| webhook list | List this account's webhook endpoints (the secret is masked) | -o, --output <format> (default table) |
| webhook get <id> | Get one webhook endpoint (the secret is masked) | -o, --output <format> (default json) |
| webhook create | Register an https:// URL for job events — prints the signing secret, onceloopback and private addresses are refused | --url <url> (required)--events <types>--description <text>-o, --output <format> (default table) |
| webhook update <id> | Change an endpoint — only what you pass changes; --enable also clears an automatic disable--events replaces the list | --url <url>--events <types>--description <text>--enable--disable-o, --output <format> (default json) |
| webhook delete <id> | Delete an endpoint — queued events are closed as FAILED and its delivery history is no longer readable (records are purged after 14 days) | -y, --yes |
| webhook rotate-secret <id> | Issue a new signing secret — printed once; the old one stops verifying immediately | -o, --output <format> (default table) |
| webhook test <id> | Send a synthetic webhook.test event now and show what the receiver answeredexit 1 unless DELIVERED | -o, --output <format> (default table) |
| webhook deliveries <id> | Recent deliveries to an endpoint, newest first — where to look when an event did not arrive | -p, --page <n> (default 0)-s, --per-page <n> (default 100)-a, --all--cursor <cursor>-o, --output <format> (default table) |
ops, models, usage, guidelines — no guessing
The catalogue is the vocabulary and it needs no key. Read it instead of remembering it: every op with its parameter contract and how it is priced, every model with its price, what your keys have spent, and the operating rules.
imagestep ops list # op · kind · job type · ✓ when it also runs synchronously · typical time per item · pricing basis · price
imagestep ops get upscale -o table # one op: every parameter with type, default and description, its pricing — and its example as a jobs submit line
imagestep models list # AI image models with prices; -m analyze for the analyze op
imagestep usage --from 2026-09-01 -g day # credits, jobs, items and sync calls; -g op|key|day; ends in a TOTAL row
imagestep guidelines # the agent operating contract, as Markdown
imagestep feedback send --kind capability_gap -m "…" --op upscale # tell us what you could not do
imagestep skill --install claude-code # the agent skill (see /docs/skill)| Command | What it does | Flags |
|---|---|---|
| models list | List available AI models | -m, --mode <mode> (default ai_image)-o, --output <format> (default table) |
| ops list | List every op in the catalogueno key | -o, --output <format> (default table) |
| ops get <op> | One op's full entry: its parameter contract and its pricingno key | -o, --output <format> (default json) |
| usage | What your keys spent: credits, jobs, items and sync calls, grouped by op, key or day | --from <date>--to <date>-g, --group-by <dimension> (default op)-o, --output <format> (default table) |
| feedback send | Report a gap (POST /api/v1/feedback) | --kind <kind> (required)-m, --message <text> (required)--op <name>--context <json>-o, --output <format> (default json) |
| feedback list | What this account has reported, newest first (GET /api/v1/feedback) | -p, --page <n> (default 0)-s, --per-page <n> (default 100)-a, --all--cursor <cursor>-o, --output <format> (default table) |
| guidelines | Print the agent operating contract (GET /api/v1/agent-guidelines; no login needed) | -o, --output <format> (default markdown) |
| skill | Print the agent skill for this CLI (a SKILL.md), or install it for a coding agentno key | --install <target> |