---
title: Ops & models
url: https://base_url.placeholder/docs/ops
group: Concepts
---

# 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

```js
const ops = await client.ops.list(); // [{ op, kind, params, defaultModel, syncEndpoint, example, … }]
```

#### Python

```python
ops = client.ops.list()  # [{"op", "kind", "params", "defaultModel", "syncEndpoint", "example", …}]
```

#### CLI

```sh
imagestep ops list -o json
```

#### curl

```sh
curl -s https://api.imagestep.dev/api/v1/ops
```

#### MCP

```text
# resource read — one op, with its full contract, is imagestep://ops/{op}
imagestep://ops
```

## Three 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](https://base_url.placeholder/docs/jobs#price), 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](https://base_url.placeholder/docs/ops/remove-background) · [Upscale](https://base_url.placeholder/docs/ops/upscale) · [Generate](https://base_url.placeholder/docs/ops/generate) · [Edit with a prompt](https://base_url.placeholder/docs/ops/edit) · [Restore faces](https://base_url.placeholder/docs/ops/restore-face) · [Colorize](https://base_url.placeholder/docs/ops/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).

The catalogue did not answer this page just now. `GET https://api.imagestep.dev/api/v1/ops` — the call at the top of the page — reads it without a key, every entry whole.

## 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](https://base_url.placeholder/pricing).

## Combining ops

One op per call. Two ops in a row are a [preset](https://base_url.placeholder/docs/presets): 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](https://base_url.placeholder/docs/presets#inline)). Several images with one op are one job with several `assetIds`; several sizes from one image are one job with [variants](https://base_url.placeholder/docs/jobs#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](https://base_url.placeholder/docs/presets/steps).

## 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:

```json
{
  "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 |
