Ops & models
An op does one thing to an image — remove the background, resize, convert — with a closed, validated set of parameters and a price per item. The catalogue below is the one vocabulary: op on POST /api/v1/jobs, the MCP transform tool, the n8n node's Op dropdown, the CLI's jobs submit --op (and its image subcommands for the ops that run synchronously) and the SDKs' ops.run all take a name from it, so an op added here works everywhere the day it ships. It answers without a key.
JavaScript
const ops = await client.ops.list(); // [{ op, kind, params, defaultModel, syncEndpoint, example, … }]Python
ops = client.ops.list() # [{"op", "kind", "params", "defaultModel", "syncEndpoint", "example", …}]CLI
imagestep ops list -o jsoncurl
curl -s https://api.imagestep.dev/api/v1/opsMCP
# resource read — one op, with its full contract, is imagestep://ops/{op}
imagestep://opsThree kinds
| kind | What it is | Runs as | Costs |
|---|---|---|---|
| ai | A model does the work: generate, edit, remove_bg, upscale, restore_face, colorize, analyze. Each has a default model you can override (Models, below). | always a job — a provider call needs an owner for its retry and refund. With wait, a one-image job still answers in the same HTTP call | credits per item, at the model's per-image USD price |
| deterministic | No model, the same output for the same input every time: resize, convert, compress, crop, pad, grayscale, rotate, flip, flop, trim, overlay, caption, mask, blur_region, adjust, flatten, render_template. render_template draws an HTML template in a headless browser, one PNG per row of data; the rest are pixel work on Sharp, starting from an image. | a job when you want the result kept as an asset; synchronously (bytes in, bytes out, nothing stored) when you only want it back — every one but overlay, which reads a stored layer and runs as a job only | free on paid plans; on Free, counted against the monthly allowance and paid from the balance past it (the catalogue's pricing.overageCredits) |
| sync | Reads, not transforms: read_metadata. Answered from the asset record or from the bytes you send. | never a job; GET /api/v1/assets/{id} or POST /api/v1/images/metadata | free |
The catalogue says how an op is priced. The exact price of one request — another model, an upscale's scale factor, a batch of forty — is the dry run, which prices the body you are about to send without creating anything.
The catalogue
Read live from GET https://api.imagestep.dev/api/v1/ops, one card per op. Six of them have a page of their own — Remove background · Upscale · Generate · Edit with a prompt · Restore faces · Colorize — with when to reach for it, a workflow around it and the same call in six surfaces. Each card ends with the catalogue's example: the body POST /api/v1/jobs takes, which every SDK, the CLI, the MCP server and the n8n node post as it is.
Every deterministic op but render_template also takes four shared parameters, at the end of its table: metadata (strip, the default, removes EXIF, ICC and XMP — a phone's GPS position included; keep carries them over), frame (the first frame of an animation, or all), colorSpace (srgb) and density (the DPI an SVG is drawn at).
generate · edit · remove_bg · upscale · restore_face · colorize · analyze · resize · convert · compress · crop · pad · grayscale · rotate · flip · flop · trim · overlay · caption · mask · blur_region · adjust · flatten · render_template · read_metadata
generate
ai job type ai-generate · typically ~10 s per item · input scaled to ≤ 1536 px on the long edgeText → image. Returns one asset per generated image.
| Parameter | Type | What it does |
|---|---|---|
| prompt | string | What to draw. |
| count | integer | Images to generate (1–10). default 1 |
| model | string | A model whose categories include 'image_generate' — GET /api/v1/ai-models lists them. Any other model is 400 invalid_param, with the ones that fit in details.allowed. default google/gemini-3.1-flash-image-preview |
| parameters | object | Passed to the model; GET /api/v1/ai-models lists each model's own, and a key or value it does not declare is 400 invalid_param. The default model reads aspectRatio and imageSize — the output size, whatever the input's, and what its price depends on. |
AI op: per-item USD from the model's price; credits are charged per item. Price a batch with POST /api/v1/jobs?dryRun=true before spending. Default model google/gemini-3.1-flash-image-preview: $0.0567 - $0.1903 per item.
The request — the catalogue's example
{
"op": "generate",
"prompt": "a red bicycle on a white background",
"count": 1
}generate in depth — when to reach for it, what a workflow around it looks like, and the same call in six surfaces.
edit
ai job type ai-edit · input scaled to ≤ 1536 px on the long edgeImage + prompt → image (instruction edit, style transfer, inpaint by description).
| Parameter | Type | What it does |
|---|---|---|
| prompt | string | The edit instruction. |
| model | string | A model whose categories include 'image_edit' — GET /api/v1/ai-models lists them. Any other model is 400 invalid_param, with the ones that fit in details.allowed. default google/gemini-3.1-flash-image-preview |
| parameters | object | Passed to the model; GET /api/v1/ai-models lists each model's own, and a key or value it does not declare is 400 invalid_param. The default model reads aspectRatio and imageSize — the output size, whatever the input's, and what its price depends on. |
AI op: per-item USD from the model's price; credits are charged per item. Price a batch with POST /api/v1/jobs?dryRun=true before spending. Default model google/gemini-3.1-flash-image-preview: $0.0567 - $0.1903 per item.
The request — the catalogue's example
{
"op": "edit",
"assetIds": [
"ast_1a2b"
],
"prompt": "replace the background with a plain warm-grey studio wall"
}edit in depth — when to reach for it, what a workflow around it looks like, and the same call in six surfaces.
remove_bg
ai job type ai-edit · typically ~5 s per item · input scaled to ≤ 2048 px on the long edgeCut the subject out; transparent PNG result.
| Parameter | Type | What it does |
|---|---|---|
| model | string | A model whose categories include 'background_removal' — GET /api/v1/ai-models lists them. Any other model is 400 invalid_param, with the ones that fit in details.allowed. default fal-ai/bria/background/remove |
AI op: per-item USD from the model's price; credits are charged per item. Price a batch with POST /api/v1/jobs?dryRun=true before spending. Default model fal-ai/bria/background/remove: $0.0227 per item.
The request — the catalogue's example
{
"op": "remove_bg",
"assetIds": [
"ast_1a2b"
]
}remove_bg in depth — when to reach for it, what a workflow around it looks like, and the same call in six surfaces.
upscale
ai job type ai-edit · typically ~15 s per item · input scaled to ≤ 1024 px on the long edgeIncrease resolution by parameters.scaleFactor, regenerating detail rather than interpolating it. The model is handed at most maxInputEdge px on the long edge, so that times the factor is the largest result.
| Parameter | Type | What it does |
|---|---|---|
| model | string | A model whose categories include 'upscale' — GET /api/v1/ai-models lists them. Any other model is 400 invalid_param, with the ones that fit in details.allowed. default fal-ai/clarity-upscaler |
| parameters | object | Passed to the model. The default model reads scaleFactor, 2 (the default) or 4 — any other value is 400 invalid_param — and resemblance, how closely the result keeps to the input. Another model's are its parameters in GET /api/v1/ai-models; a key or value a model does not declare is 400 invalid_param. |
AI op: per-item USD from the model's price; credits are charged per item. Price a batch with POST /api/v1/jobs?dryRun=true before spending. Default model fal-ai/clarity-upscaler: $0.1512 - $0.6048 per item.
The request — the catalogue's example
{
"op": "upscale",
"assetIds": [
"ast_1a2b"
],
"parameters": {
"scaleFactor": 2
}
}upscale in depth — when to reach for it, what a workflow around it looks like, and the same call in six surfaces.
restore_face
ai job type ai-edit · input scaled to ≤ 1024 px on the long edgeFix faces in old or low-quality photos. The default model also enlarges the result, by parameters.scaleFactor.
| Parameter | Type | What it does |
|---|---|---|
| model | string | A model whose categories include 'face_restoration' — GET /api/v1/ai-models lists them. Any other model is 400 invalid_param, with the ones that fit in details.allowed. default replicate/tencentarc/gfpgan |
| parameters | object | Passed to the model. The default model reads scaleFactor — how many times the size it was handed the repaired image comes back, 2 unless set, 1 to keep it — and version, the GFPGAN release to run. Another model's are its parameters in GET /api/v1/ai-models; a key or value a model does not declare is 400 invalid_param. |
AI op: per-item USD from the model's price; credits are charged per item. Price a batch with POST /api/v1/jobs?dryRun=true before spending. Default model replicate/tencentarc/gfpgan: $0.0028 per item.
The request — the catalogue's example
{
"op": "restore_face",
"assetIds": [
"ast_1a2b"
],
"parameters": {
"scaleFactor": 2
}
}restore_face in depth — when to reach for it, what a workflow around it looks like, and the same call in six surfaces.
colorize
ai job type ai-edit · input scaled to ≤ 2048 px on the long edgeColour a black-and-white photo.
| Parameter | Type | What it does |
|---|---|---|
| model | string | A model whose categories include 'colorize' — GET /api/v1/ai-models lists them. Any other model is 400 invalid_param, with the ones that fit in details.allowed. default replicate/piddnad/ddcolor |
AI op: per-item USD from the model's price; credits are charged per item. Price a batch with POST /api/v1/jobs?dryRun=true before spending. Default model replicate/piddnad/ddcolor: $0.0013 per item.
The request — the catalogue's example
{
"op": "colorize",
"assetIds": [
"ast_1a2b"
]
}colorize in depth — when to reach for it, what a workflow around it looks like, and the same call in six surfaces.
analyze
ai job type parse · input scaled to ≤ 1024 px on the long edge · answers with JSON, not an imageImage → structured JSON: a description, search tags, objects, the scene and any legible text, or the shape you pass as parameters.schema. Each item's answer is its output (items[].output on GET /api/v1/jobs/{id} and on the job.item.completed webhook). With the default schema, the answer's tags and objects are also added to the asset's tags, so GET /api/v1/assets?tag= finds it; a schema of your own leaves the asset alone. Produces no new asset.
| Parameter | Type | What it does |
|---|---|---|
| prompt | string | What to look for (≤ 2000 bytes). Default: describe the image and tag it for search. |
| model | string | Any model from GET /api/v1/ai-models?mode=analyze. default google/gemini-2.5-flash-lite |
| parameters | object | {schema: a JSON Schema object the answer must match (≤ 6000 bytes), maxOutputTokens: 256–4096, default 1024}. The price is per image at maxOutputTokens, whatever the prompt and schema say. |
AI op: per-item USD from the model's price; credits are charged per item. Price a batch with POST /api/v1/jobs?dryRun=true before spending. Default model google/gemini-2.5-flash-lite: $0.0013 per item.
The request — the catalogue's example
{
"op": "analyze",
"assetIds": [
"ast_1a2b"
],
"prompt": "Is there a person in the picture? Describe what they are wearing."
}resize
deterministic job type process · sync POST /api/v1/images/transform · typically ~3 s per itemScale to a box. Keeps aspect ratio unless fit=fill. Crop to an exact box with fit=cover, and let gravity=attention choose what to keep; fit=contain pads to the exact box instead.
| Parameter | Type | What it does |
|---|---|---|
| width | integer | Target width in px (one of width/height required). |
| height | integer | Target height in px. |
| fit | string | cover · contain · fill · inside · outside default inside |
| gravity | string | Which part cover keeps, or where contain places the image: the nine compass points, or attention / entropy to pick the busiest region. Only meaningful with fit=cover or fit=contain. default centre |
| background | string | The letterbox colour for fit=contain. Default: transparent, or white when the result is a JPEG. Only meaningful with fit=contain. |
| withoutEnlargement | boolean | Never upscale. With fit=cover or fill and a box bigger than the image, the box shrinks to the biggest one of the same shape the image covers (1080×1350 on a 1024×1024 image → 819×1024): the output is smaller than asked, never a different shape. false enlarges to the exact box. default true |
| matchOrientation | boolean | Turn the box to the image's orientation first: a portrait image asked for 1800×1200 is resized to 1200×1800, a landscape one to 1800×1200 — one step for a print size or a frame, whichever way the photo was shot. Judged on the image as the resize receives it (upright, after any rotate or crop before it); a square image or a square box is never turned. Needs width and height. default false |
| metadata | string | strip (default) removes EXIF, ICC and XMP — including the GPS coordinates a phone writes. keep carries all of it into the output. default strip |
| frame | string | first (default) reads only the first frame of an animation — which is why a resized GIF used to stop moving. all keeps every frame, and then the output format has to be one that animates (gif or webp). default first |
| colorSpace | string | srgb converts the pixels to sRGB and attaches the profile. There is one value because there is one colour space the web assumes: a CMYK or Adobe RGB original published as-is goes grey, and this is the only fix. |
| density | integer | Dots per inch to rasterise a VECTOR input (SVG) at. A drawing has no pixels until something picks a scale, and 72 is what libvips picks; raise it to draw the vector larger instead of upscaling a small raster. Ignored for photographs. default 72 |
Deterministic op: unlimited on paid plans; Free counts it against the monthly allowance and pays each run past it from the balance. Free plan: 200 per month.
The request — the catalogue's example
{
"op": "resize",
"assetIds": [
"ast_1a2b"
],
"parameters": {
"width": 1200
}
}convert
deterministic job type process · sync POST /api/v1/images/transform · typically ~3 s per itemChange the file format.
| Parameter | Type | What it does |
|---|---|---|
| format | string | webp · jpeg · png · avif · gif · tiff (required) |
| quality | integer | 1–100, lossy formats only. default 82 |
| background | string | What transparency becomes when the target format has no alpha channel (jpeg). Ignored by formats that keep it. default #ffffff |
| metadata | string | strip (default) removes EXIF, ICC and XMP — including the GPS coordinates a phone writes. keep carries all of it into the output. default strip |
| frame | string | first (default) reads only the first frame of an animation — which is why a resized GIF used to stop moving. all keeps every frame, and then the output format has to be one that animates (gif or webp). default first |
| colorSpace | string | srgb converts the pixels to sRGB and attaches the profile. There is one value because there is one colour space the web assumes: a CMYK or Adobe RGB original published as-is goes grey, and this is the only fix. |
| density | integer | Dots per inch to rasterise a VECTOR input (SVG) at. A drawing has no pixels until something picks a scale, and 72 is what libvips picks; raise it to draw the vector larger instead of upscaling a small raster. Ignored for photographs. default 72 |
Deterministic op: unlimited on paid plans; Free counts it against the monthly allowance and pays each run past it from the balance. Free plan: 200 per month.
The request — the catalogue's example
{
"op": "convert",
"assetIds": [
"ast_1a2b"
],
"parameters": {
"format": "webp",
"quality": 82
}
}compress
deterministic job type process · sync POST /api/v1/images/transform · typically ~3 s per itemRe-encode smaller — at a quality target, or at a file-size target.
| Parameter | Type | What it does |
|---|---|---|
| quality | integer | 1–100. default 75 |
| format | string | webp · jpeg · png · avif default webp |
| maxBytes | integer | Hit this file size instead of a quality: the encoder is run again at lower quality until the result fits. Lossy formats only (webp · jpeg · avif). A size this image cannot reach is 400 invalid_param, never a blurry near-miss. |
| background | string | What transparency becomes when writing jpeg. default #ffffff |
| metadata | string | strip (default) removes EXIF, ICC and XMP — including the GPS coordinates a phone writes. keep carries all of it into the output. default strip |
| frame | string | first (default) reads only the first frame of an animation — which is why a resized GIF used to stop moving. all keeps every frame, and then the output format has to be one that animates (gif or webp). default first |
| colorSpace | string | srgb converts the pixels to sRGB and attaches the profile. There is one value because there is one colour space the web assumes: a CMYK or Adobe RGB original published as-is goes grey, and this is the only fix. |
| density | integer | Dots per inch to rasterise a VECTOR input (SVG) at. A drawing has no pixels until something picks a scale, and 72 is what libvips picks; raise it to draw the vector larger instead of upscaling a small raster. Ignored for photographs. default 72 |
Deterministic op: unlimited on paid plans; Free counts it against the monthly allowance and pays each run past it from the balance. Free plan: 200 per month.
The request — the catalogue's example
{
"op": "compress",
"assetIds": [
"ast_1a2b"
],
"parameters": {
"maxBytes": 200000
}
}crop
deterministic job type process · sync POST /api/v1/images/transform · typically ~3 s per itemCut a rectangle out of the image at an exact offset. To crop to a size without knowing the offset, use resize with fit=cover and a gravity.
| Parameter | Type | What it does |
|---|---|---|
| left | integer | Offset from the left edge. default 0 |
| top | integer | Offset from the top edge. default 0 |
| width | integer | Crop width (required). |
| height | integer | Crop height (required). |
| metadata | string | strip (default) removes EXIF, ICC and XMP — including the GPS coordinates a phone writes. keep carries all of it into the output. default strip |
| frame | string | first (default) reads only the first frame of an animation — which is why a resized GIF used to stop moving. all keeps every frame, and then the output format has to be one that animates (gif or webp). default first |
| colorSpace | string | srgb converts the pixels to sRGB and attaches the profile. There is one value because there is one colour space the web assumes: a CMYK or Adobe RGB original published as-is goes grey, and this is the only fix. |
| density | integer | Dots per inch to rasterise a VECTOR input (SVG) at. A drawing has no pixels until something picks a scale, and 72 is what libvips picks; raise it to draw the vector larger instead of upscaling a small raster. Ignored for photographs. default 72 |
Deterministic op: unlimited on paid plans; Free counts it against the monthly allowance and pays each run past it from the balance. Free plan: 200 per month.
The request — the catalogue's example
{
"op": "crop",
"assetIds": [
"ast_1a2b"
],
"parameters": {
"left": 100,
"top": 50,
"width": 800,
"height": 800
}
}pad
deterministic job type process · sync POST /api/v1/images/transform · typically ~3 s per itemExtend the canvas with a background colour (transparent by default).
| Parameter | Type | What it does |
|---|---|---|
| top | integer | default 0 |
| bottom | integer | default 0 |
| left | integer | default 0 |
| right | integer | default 0 |
| background | string | CSS colour. Default: transparent, or white when the result is a JPEG, which has no alpha channel. |
| metadata | string | strip (default) removes EXIF, ICC and XMP — including the GPS coordinates a phone writes. keep carries all of it into the output. default strip |
| frame | string | first (default) reads only the first frame of an animation — which is why a resized GIF used to stop moving. all keeps every frame, and then the output format has to be one that animates (gif or webp). default first |
| colorSpace | string | srgb converts the pixels to sRGB and attaches the profile. There is one value because there is one colour space the web assumes: a CMYK or Adobe RGB original published as-is goes grey, and this is the only fix. |
| density | integer | Dots per inch to rasterise a VECTOR input (SVG) at. A drawing has no pixels until something picks a scale, and 72 is what libvips picks; raise it to draw the vector larger instead of upscaling a small raster. Ignored for photographs. default 72 |
Deterministic op: unlimited on paid plans; Free counts it against the monthly allowance and pays each run past it from the balance. Free plan: 200 per month.
The request — the catalogue's example
{
"op": "pad",
"assetIds": [
"ast_1a2b"
],
"parameters": {
"top": 40,
"bottom": 40,
"left": 40,
"right": 40,
"background": "#ffffff"
}
}grayscale
deterministic job type process · sync POST /api/v1/images/transform · typically ~3 s per itemConvert to greyscale.
| Parameter | Type | What it does |
|---|---|---|
| metadata | string | strip (default) removes EXIF, ICC and XMP — including the GPS coordinates a phone writes. keep carries all of it into the output. default strip |
| frame | string | first (default) reads only the first frame of an animation — which is why a resized GIF used to stop moving. all keeps every frame, and then the output format has to be one that animates (gif or webp). default first |
| colorSpace | string | srgb converts the pixels to sRGB and attaches the profile. There is one value because there is one colour space the web assumes: a CMYK or Adobe RGB original published as-is goes grey, and this is the only fix. |
| density | integer | Dots per inch to rasterise a VECTOR input (SVG) at. A drawing has no pixels until something picks a scale, and 72 is what libvips picks; raise it to draw the vector larger instead of upscaling a small raster. Ignored for photographs. default 72 |
Deterministic op: unlimited on paid plans; Free counts it against the monthly allowance and pays each run past it from the balance. Free plan: 200 per month.
The request — the catalogue's example
{
"op": "grayscale",
"assetIds": [
"ast_1a2b"
]
}rotate
deterministic job type process · sync POST /api/v1/images/transform · typically ~3 s per itemTurn the image. Multiples of 90 are lossless; any other angle fills the corners.
| Parameter | Type | What it does |
|---|---|---|
| angle | integer | Degrees clockwise, -360 to 360 (required). |
| background | string | Corner fill for angles that are not a multiple of 90. Default: transparent, or white when the result is a JPEG. |
| metadata | string | strip (default) removes EXIF, ICC and XMP — including the GPS coordinates a phone writes. keep carries all of it into the output. default strip |
| frame | string | first (default) reads only the first frame of an animation — which is why a resized GIF used to stop moving. all keeps every frame, and then the output format has to be one that animates (gif or webp). default first |
| colorSpace | string | srgb converts the pixels to sRGB and attaches the profile. There is one value because there is one colour space the web assumes: a CMYK or Adobe RGB original published as-is goes grey, and this is the only fix. |
| density | integer | Dots per inch to rasterise a VECTOR input (SVG) at. A drawing has no pixels until something picks a scale, and 72 is what libvips picks; raise it to draw the vector larger instead of upscaling a small raster. Ignored for photographs. default 72 |
Deterministic op: unlimited on paid plans; Free counts it against the monthly allowance and pays each run past it from the balance. Free plan: 200 per month.
The request — the catalogue's example
{
"op": "rotate",
"assetIds": [
"ast_1a2b"
],
"parameters": {
"angle": 90
}
}flip
deterministic job type process · sync POST /api/v1/images/transform · typically ~3 s per itemMirror top to bottom.
| Parameter | Type | What it does |
|---|---|---|
| metadata | string | strip (default) removes EXIF, ICC and XMP — including the GPS coordinates a phone writes. keep carries all of it into the output. default strip |
| frame | string | first (default) reads only the first frame of an animation — which is why a resized GIF used to stop moving. all keeps every frame, and then the output format has to be one that animates (gif or webp). default first |
| colorSpace | string | srgb converts the pixels to sRGB and attaches the profile. There is one value because there is one colour space the web assumes: a CMYK or Adobe RGB original published as-is goes grey, and this is the only fix. |
| density | integer | Dots per inch to rasterise a VECTOR input (SVG) at. A drawing has no pixels until something picks a scale, and 72 is what libvips picks; raise it to draw the vector larger instead of upscaling a small raster. Ignored for photographs. default 72 |
Deterministic op: unlimited on paid plans; Free counts it against the monthly allowance and pays each run past it from the balance. Free plan: 200 per month.
The request — the catalogue's example
{
"op": "flip",
"assetIds": [
"ast_1a2b"
]
}flop
deterministic job type process · sync POST /api/v1/images/transform · typically ~3 s per itemMirror left to right.
| Parameter | Type | What it does |
|---|---|---|
| metadata | string | strip (default) removes EXIF, ICC and XMP — including the GPS coordinates a phone writes. keep carries all of it into the output. default strip |
| frame | string | first (default) reads only the first frame of an animation — which is why a resized GIF used to stop moving. all keeps every frame, and then the output format has to be one that animates (gif or webp). default first |
| colorSpace | string | srgb converts the pixels to sRGB and attaches the profile. There is one value because there is one colour space the web assumes: a CMYK or Adobe RGB original published as-is goes grey, and this is the only fix. |
| density | integer | Dots per inch to rasterise a VECTOR input (SVG) at. A drawing has no pixels until something picks a scale, and 72 is what libvips picks; raise it to draw the vector larger instead of upscaling a small raster. Ignored for photographs. default 72 |
Deterministic op: unlimited on paid plans; Free counts it against the monthly allowance and pays each run past it from the balance. Free plan: 200 per month.
The request — the catalogue's example
{
"op": "flop",
"assetIds": [
"ast_1a2b"
]
}trim
deterministic job type process · sync POST /api/v1/images/transform · typically ~3 s per itemCut away a uniform border — the whitespace around a cut-out, or a solid matte.
| Parameter | Type | What it does |
|---|---|---|
| threshold | integer | How different from the border colour a pixel must be to be kept, 0–255. default 10 |
| background | string | The border colour. Defaults to the top-left pixel. |
| metadata | string | strip (default) removes EXIF, ICC and XMP — including the GPS coordinates a phone writes. keep carries all of it into the output. default strip |
| frame | string | first (default) reads only the first frame of an animation — which is why a resized GIF used to stop moving. all keeps every frame, and then the output format has to be one that animates (gif or webp). default first |
| colorSpace | string | srgb converts the pixels to sRGB and attaches the profile. There is one value because there is one colour space the web assumes: a CMYK or Adobe RGB original published as-is goes grey, and this is the only fix. |
| density | integer | Dots per inch to rasterise a VECTOR input (SVG) at. A drawing has no pixels until something picks a scale, and 72 is what libvips picks; raise it to draw the vector larger instead of upscaling a small raster. Ignored for photographs. default 72 |
Deterministic op: unlimited on paid plans; Free counts it against the monthly allowance and pays each run past it from the balance. Free plan: 200 per month.
The request — the catalogue's example
{
"op": "trim",
"assetIds": [
"ast_1a2b"
],
"parameters": {
"threshold": 10
}
}overlay
deterministic job type process · typically ~3 s per itemPut another image on top — a logo, a watermark. Job only: it reads a stored layer, and the synchronous endpoints hold no credential for one.
| Parameter | Type | What it does |
|---|---|---|
| layerAssetId | string | Asset id of the layer, one of your own (required). |
| gravity | string | Where it sits: the nine compass points. default southeast |
| scale | number | The layer's share of the base width, above 0 and at most 1. A layer that would come out taller than the image is scaled down to fit. default 0.25 |
| opacity | number | Above 0 and at most 1. default 1 |
| margin | integer | Distance from the edge, in px. default 16 |
| tile | boolean | Repeat across the whole image; gravity and margin then do nothing. default false |
| metadata | string | strip (default) removes EXIF, ICC and XMP — including the GPS coordinates a phone writes. keep carries all of it into the output. default strip |
| frame | string | first (default) reads only the first frame of an animation — which is why a resized GIF used to stop moving. all keeps every frame, and then the output format has to be one that animates (gif or webp). default first |
| colorSpace | string | srgb converts the pixels to sRGB and attaches the profile. There is one value because there is one colour space the web assumes: a CMYK or Adobe RGB original published as-is goes grey, and this is the only fix. |
| density | integer | Dots per inch to rasterise a VECTOR input (SVG) at. A drawing has no pixels until something picks a scale, and 72 is what libvips picks; raise it to draw the vector larger instead of upscaling a small raster. Ignored for photographs. default 72 |
Deterministic op: unlimited on paid plans; Free counts it against the monthly allowance and pays each run past it from the balance. Free plan: 200 per month.
The request — the catalogue's example
{
"op": "overlay",
"assetIds": [
"ast_1a2b"
],
"parameters": {
"layerAssetId": "ast_9z8y",
"gravity": "southeast"
}
}caption
deterministic job type process · sync POST /api/v1/images/transform · typically ~3 s per itemWrite a line of text on the image — a price, a title, a label. For anything with a layout, use render_template.
| Parameter | Type | What it does |
|---|---|---|
| text | string | The text (required). Written as content, not markup. |
| font | string | A font family installed in the worker image. default sans |
| size | integer | Point size, at most: text that would not fit the image (a long word, a small picture) is scaled down to fit. Defaults to a twentieth of the image width. |
| color | string | CSS colour. default #ffffff |
| background | string | A plate behind the text. Without one, light text on a light photo is unreadable. |
| gravity | string | The nine compass points. default southeast |
| margin | integer | Distance from the edge, in px. default 16 |
| metadata | string | strip (default) removes EXIF, ICC and XMP — including the GPS coordinates a phone writes. keep carries all of it into the output. default strip |
| frame | string | first (default) reads only the first frame of an animation — which is why a resized GIF used to stop moving. all keeps every frame, and then the output format has to be one that animates (gif or webp). default first |
| colorSpace | string | srgb converts the pixels to sRGB and attaches the profile. There is one value because there is one colour space the web assumes: a CMYK or Adobe RGB original published as-is goes grey, and this is the only fix. |
| density | integer | Dots per inch to rasterise a VECTOR input (SVG) at. A drawing has no pixels until something picks a scale, and 72 is what libvips picks; raise it to draw the vector larger instead of upscaling a small raster. Ignored for photographs. default 72 |
Deterministic op: unlimited on paid plans; Free counts it against the monthly allowance and pays each run past it from the balance. Free plan: 200 per month.
The request — the catalogue's example
{
"op": "caption",
"assetIds": [
"ast_1a2b"
],
"parameters": {
"text": "$39",
"gravity": "southeast"
}
}mask
deterministic job type process · sync POST /api/v1/images/transform · typically ~3 s per itemCut the image to a shape — a round avatar, a card with rounded corners. The result carries transparency, so it needs a format that has an alpha channel.
| Parameter | Type | What it does |
|---|---|---|
| shape | string | circle · rounded default circle |
| radius | integer | Corner radius in px, required for shape=rounded. |
| metadata | string | strip (default) removes EXIF, ICC and XMP — including the GPS coordinates a phone writes. keep carries all of it into the output. default strip |
| frame | string | first (default) reads only the first frame of an animation — which is why a resized GIF used to stop moving. all keeps every frame, and then the output format has to be one that animates (gif or webp). default first |
| colorSpace | string | srgb converts the pixels to sRGB and attaches the profile. There is one value because there is one colour space the web assumes: a CMYK or Adobe RGB original published as-is goes grey, and this is the only fix. |
| density | integer | Dots per inch to rasterise a VECTOR input (SVG) at. A drawing has no pixels until something picks a scale, and 72 is what libvips picks; raise it to draw the vector larger instead of upscaling a small raster. Ignored for photographs. default 72 |
Deterministic op: unlimited on paid plans; Free counts it against the monthly allowance and pays each run past it from the balance. Free plan: 200 per month.
The request — the catalogue's example
{
"op": "mask",
"assetIds": [
"ast_1a2b"
],
"parameters": {
"shape": "circle"
}
}blur_region
deterministic job type process · sync POST /api/v1/images/transform · typically ~3 s per itemBlur or pixelate one rectangle — a face, a licence plate, an address.
| Parameter | Type | What it does |
|---|---|---|
| left | integer | Offset from the left edge. default 0 |
| top | integer | Offset from the top edge. default 0 |
| width | integer | Region width (required). |
| height | integer | Region height (required). |
| sigma | number | Blur strength, 0.3–1000. default 12 |
| pixelate | integer | Block size in px. Blocks instead of a blur — a blur can be sharpened back, blocks cannot. |
| metadata | string | strip (default) removes EXIF, ICC and XMP — including the GPS coordinates a phone writes. keep carries all of it into the output. default strip |
| frame | string | first (default) reads only the first frame of an animation — which is why a resized GIF used to stop moving. all keeps every frame, and then the output format has to be one that animates (gif or webp). default first |
| colorSpace | string | srgb converts the pixels to sRGB and attaches the profile. There is one value because there is one colour space the web assumes: a CMYK or Adobe RGB original published as-is goes grey, and this is the only fix. |
| density | integer | Dots per inch to rasterise a VECTOR input (SVG) at. A drawing has no pixels until something picks a scale, and 72 is what libvips picks; raise it to draw the vector larger instead of upscaling a small raster. Ignored for photographs. default 72 |
Deterministic op: unlimited on paid plans; Free counts it against the monthly allowance and pays each run past it from the balance. Free plan: 200 per month.
The request — the catalogue's example
{
"op": "blur_region",
"assetIds": [
"ast_1a2b"
],
"parameters": {
"left": 120,
"top": 80,
"width": 200,
"height": 60
}
}adjust
deterministic job type process · sync POST /api/v1/images/transform · typically ~3 s per itemBrightness, contrast, saturation, hue — the batch correction a shelf of product photos needs. At least one of them is required.
| Parameter | Type | What it does |
|---|---|---|
| brightness | number | Multiplier, 0.1–10. 1 is unchanged. |
| saturation | number | Multiplier, 0–10. 0 is greyscale. |
| hue | number | Rotation in degrees, -360 to 360. |
| lightness | number | Added, -100 to 100. |
| contrast | number | Multiplier around mid-grey, 0.1–10. 1 is unchanged. |
| metadata | string | strip (default) removes EXIF, ICC and XMP — including the GPS coordinates a phone writes. keep carries all of it into the output. default strip |
| frame | string | first (default) reads only the first frame of an animation — which is why a resized GIF used to stop moving. all keeps every frame, and then the output format has to be one that animates (gif or webp). default first |
| colorSpace | string | srgb converts the pixels to sRGB and attaches the profile. There is one value because there is one colour space the web assumes: a CMYK or Adobe RGB original published as-is goes grey, and this is the only fix. |
| density | integer | Dots per inch to rasterise a VECTOR input (SVG) at. A drawing has no pixels until something picks a scale, and 72 is what libvips picks; raise it to draw the vector larger instead of upscaling a small raster. Ignored for photographs. default 72 |
Deterministic op: unlimited on paid plans; Free counts it against the monthly allowance and pays each run past it from the balance. Free plan: 200 per month.
The request — the catalogue's example
{
"op": "adjust",
"assetIds": [
"ast_1a2b"
],
"parameters": {
"brightness": 1.1
}
}flatten
deterministic job type process · sync POST /api/v1/images/transform · typically ~3 s per itemComposite transparency onto a solid colour — what a marketplace means by "white background".
| Parameter | Type | What it does |
|---|---|---|
| background | string | CSS colour. default #ffffff |
| metadata | string | strip (default) removes EXIF, ICC and XMP — including the GPS coordinates a phone writes. keep carries all of it into the output. default strip |
| frame | string | first (default) reads only the first frame of an animation — which is why a resized GIF used to stop moving. all keeps every frame, and then the output format has to be one that animates (gif or webp). default first |
| colorSpace | string | srgb converts the pixels to sRGB and attaches the profile. There is one value because there is one colour space the web assumes: a CMYK or Adobe RGB original published as-is goes grey, and this is the only fix. |
| density | integer | Dots per inch to rasterise a VECTOR input (SVG) at. A drawing has no pixels until something picks a scale, and 72 is what libvips picks; raise it to draw the vector larger instead of upscaling a small raster. Ignored for photographs. default 72 |
Deterministic op: unlimited on paid plans; Free counts it against the monthly allowance and pays each run past it from the balance. Free plan: 200 per month.
The request — the catalogue's example
{
"op": "flatten",
"assetIds": [
"ast_1a2b"
],
"parameters": {
"background": "#ffffff"
}
}render_template
deterministic job type render · sync POST /api/v1/images/renderRender an HTML/CSS template once per item of data → one PNG asset per item. Templates: POST /api/v1/templates ({{ var }} placeholders, HTML-escaped; {{{ var }}} raw).
| Parameter | Type | What it does |
|---|---|---|
| templateId | string | Template id from GET /api/v1/templates, or id@version to pin a version (required). |
| items | array | One object of template variables per output image, 1–500 (required). |
Deterministic op: unlimited on paid plans; Free counts it against the monthly allowance and pays each run past it from the balance. Free plan: 200 per month.
The request — the catalogue's example
{
"op": "render_template",
"templateId": "builtin-template-og-image",
"items": [
{
"title": "Hello"
}
]
}read_metadata
sync sync POST /api/v1/images/metadata · answers with JSON, not an imageEXIF, GPS, dimensions, hash and mime type — extracted when the asset was ingested; no job needed.
Answered by GET /api/v1/assets/{id} (fields image, metadata)
Free. Read on every asset, any plan.
Models
An AI op runs on a model. Its entry names the default (defaultModel, priced under pricing.defaultModel); a request's own model overrides it, but only with a model sold for that op — one whose categories include the entry's modelCategory. Any other is 400 invalid_param, with the models that fit in details.allowed. Models come in two modes: ai_image for generate, edit and the other image ops, and analyze for the analyze op.
Every model is priced per image in USD: the provider's list price times 1 + markup, and credits are that times creditsPerUsd — all three are in the entry. providerPrice is the provider's price as we last recorded it, not a live quote. An analyze model's price is per image too, fixed before it runs: its providerPrice is the most one request can use — the image, a prompt and schema at their limits, and maxOutputTokens — so a typical request costs us well under it, and the dry run, the job and the charge are the one number.
| read it from | what you get |
|---|---|
| GET https://api.imagestep.dev/api/pricing/image-models | every model with priceFrom / priceRange and providerPrice — public, no key |
| GET /api/v1/ai-models?mode=ai_image|analyze | the same models with an API key, plus what a call needs to pick one: its categories, its own parameters (the keys an AI op's parameters takes, with defaults and allowed values) and max_input_edge |
| imagestep models list | the CLI; -m analyze for the analyze models |
| imagestep://models/{mode} | the MCP resource, for a client that cannot curl |
Where a price is a range, the provider's is too: an upscaler billed by output megapixel costs more at 4× than at 2×, an image model more at 4K than at 1K. priceFrom is the cheapest setting, which is not always what a call that sets nothing gets — the dry run prices the parameters you actually send. A model nobody can price-check is not in the catalogue. The human-readable table, plans included, is /pricing.
Combining ops
One op per call. Two ops in a row are a preset: a saved, versioned list of steps that runs as one job (or, when every step can run synchronously, in one synchronous call) — or the same steps sent inline, when a run needs no name or version (without saving them). Several images with one op are one job with several assetIds; several sizes from one image are one job with variants, which a deterministic op takes. What an op cannot express — a sharpen, a colour matrix — a preset step names from the engine's step registry.
What an entry carries
Every entry is the whole contract for its op, so nothing has to be guessed and nothing is hard-coded on a client. This is remove_bg's, as the service answers it:
{
"op": "remove_bg",
"kind": "ai",
"produces": "image",
"description": "Cut the subject out; transparent PNG result.",
"jobType": "ai-edit",
"requiresAssets": true,
"requiresPrompt": false,
"defaultModel": "fal-ai/bria/background/remove",
"params": {
"model": {
"type": "string",
"default": "fal-ai/bria/background/remove",
"description": "A model whose categories include 'background_removal' — GET /api/v1/ai-models lists them. Any other model is 400 invalid_param, with the ones that fit in details.allowed."
}
},
"pricing": {
"basis": "per_item",
"summary": "AI op: per-item USD from the model's price; credits are charged per item. Price a batch with POST /api/v1/jobs?dryRun=true before spending.",
"defaultModel": {
"id": "fal-ai/bria/background/remove",
"name": "Fal.ai: Bria RMBG 2.0",
"series": "Fal.ai",
"description": "Commercial-grade background removal trained exclusively on licensed data. Best for professional editing tasks requiring seamless background removal.",
"categories": [
"background_removal"
],
"priceFrom": "0.0227",
"priceRange": "$0.0227",
"providerPrice": "0.018"
},
"markup": 0.26,
"creditsPerUsd": 10000,
"exactPrice": "POST /api/v1/jobs?dryRun=true"
},
"maxInputEdge": 2048,
"modelCategory": "background_removal",
"example": {
"op": "remove_bg",
"assetIds": [
"ast_1a2b"
]
},
"typicalSeconds": 5
}| field | type | meaning |
|---|---|---|
| op | string | the name you send |
| kind | ai | deterministic | sync | which of the three kinds above |
| description | string | one line on what it does — what a dropdown or a tool list shows |
| jobType | string | the job type it submits as — the service derives it, you never send it alongside op |
| params | object | name → {type, default, description}. For a deterministic op these are the keys of parameters, a closed set: any other key is 400 invalid_param naming it, with the ones it takes in details.allowed. For an AI op they are fields of the body — prompt, count, model — plus parameters, whose keys are the model's (GET /api/v1/ai-models): a key or value the model does not declare is 400 invalid_param too. render_template's are fields of the body too |
| requiresAssets · requiresPrompt | boolean | whether it takes input images (generate and render_template do not) and whether a prompt is mandatory (generate, edit) or optional with a default (analyze) |
| produces | image | json | what one step of it hands to the step after it: an image, or json. Read it with requiresAssets before you compose a chain — an op that answers with json is its last step, and an op that reads no image is not a step of one at all (presets → the order the steps are in) |
| defaultModel | string | AI ops: the model that runs when a call names none |
| modelCategory | string | AI ops: the category a model must carry (GET /api/v1/ai-models → categories) to run this op; a model from another one is 400 invalid_param with the ones that fit in details.allowed |
| maxInputEdge | integer | AI ops: the longest edge, in px, an input image is scaled down to (never up) before the default model sees it. For remove_bg and colorize it is also the largest result; upscale and restore_face multiply it by their scale factor; generate and edit write the size parameters.imageSize asks for |
| defaultPrompt · defaultSchema | string · object | analyze: the prompt it asks and the JSON schema it answers in when a call gives neither |
| syncEndpoint | string | the synchronous endpoint that runs it without a job, or absent — every deterministic op has one but overlay, because the sync lane holds no credential for a second stored image |
| endpoint | string | a sync op: where its answer already is without sending bytes — read_metadata's is the asset record |
| example | object | one complete POST /api/v1/jobs body that runs the op — its required parameters and a placeholder asset id — checked by the validator a submit goes through. Copy it and swap the id; each entry above shows it, and every surface posts this same body. A sync op has none |
| typicalSeconds | integer | how long ONE item usually takes as a job, submit to done, on the default model — so you can size a wait (jobs → waiting) from the catalogue instead of guessing. Measured on production; a hint, not a promise, and absent where nobody has measured yet |
| pricing | OpPricing | basis (per_item · process_quota · free) and a one-line summary; for an AI op the default model's price record (id, priceFrom, priceRange, providerPrice), markup and creditsPerUsd; for a deterministic op processLimit, the monthly allowance per plan (-1 is unlimited), and overageCredits, what a run past it costs on a plan that has one; exactPrice names the dry run |