Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: ImageStep Ops API
description: 'The image step for your automations. Store images as assets, run an op or a preset on them as a job — priced first with `?dryRun=true`, then read, waited on or delivered to a webhook — or transform one image synchronously while you wait. Send `Authorization: ApiKey `; the op catalogue (`/api/v1/ops`) needs no credential.
**Idempotency:** Endpoints that accept an `Idempotency-Key` header support optional deduplication. Supply a unique key (e.g. a UUID) per logical request. Repeating the same key with the same body replays the stored response (`Idempotency-Replayed: true`) without re-running anything; repeating it with a different body is a 409 `idempotency_key_reuse`. Omitting the header processes the request normally. Keys are retained for 24 hours.
**Errors** all share one shape: `{"error": {"code", "message", "retryable", "param", "details", "requestId"}}`. Branch on `code`, and let `retryable` — not the status code — decide whether to come back.
**Rate limits** are published on every response as `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` (seconds). A 429 adds `Retry-After`.
**A paged list** takes `page` (from 0) and `perPage`, and every page answers `meta.nextCursor`; send it back as `cursor` to read on without counting.
**Every response carries `X-Request-Id`** — yours if you sent one, ours otherwise — and a failure repeats it as `error.requestId`. It is what finds the request in our logs.
The error-code table, idempotency, limits and pagination are at https://imagestep.dev/docs/errors; the webhook events at https://imagestep.dev/docs/webhooks; what may change inside v1, and the changelog, at https://imagestep.dev/docs/changelog.'
contact:
name: ImageStep Team
url: https://imagestep.dev/support
license:
name: Proprietary
url: https://imagestep.dev/terms
version: v1
servers:
- url: https://api.imagestep.dev
description: Production server
tags:
- name: Ops
description: 'The catalogue of atomic operations: what `POST /api/v1/jobs` accepts in `op`, with each op''s parameter contract, its price as data, an example request and the endpoint that runs it synchronously, if any. Read it instead of carrying a list of op names. AI ops charge credits per item; deterministic ops count against the plan''s processing allowance; a sync op names the endpoint that answers it. **No credential needed** — an agent can read the catalogue before it has a key.'
paths:
/api/v1/ops:
get:
tags:
- Ops
summary: List every atomic op with its parameter contract
description: 'Every atomic op, with what a program needs to call it without a list of its own. Submit one with
`POST /api/v1/jobs {"op": "", "assetIds": [...], "parameters": {...}}`, priced first with
`?dryRun=true`. Chaining ops is a list of steps — inline as `steps` on `POST /api/v1/jobs`, or
saved as a preset (`POST /api/v1/presets`). Answers without a credential.
What each entry carries:
- `params` — the parameter contract: name → type, bounds, default, description
- `pricing` — the price as data: `basis` (`per_item` · `process_quota` · `free`); for an AI op, `defaultModel` is that model''s published price (the record `GET /api/pricing/image-models` publishes) with `markup` and `creditsPerUsd`; for a deterministic op, `processLimit` is the allowance per billing period by plan (-1 unlimited)
- `example` — one complete, validated `POST /api/v1/jobs` body with a placeholder asset id: copy it, swap the id. Absent for a sync op, which is not a job
- `modelCategory` — AI ops: the category a `model` must carry (`GET /api/v1/ai-models` → `categories`) to run the op
- `typicalSeconds` — how long one item usually takes as a job on the default model: a measured hint for sizing `wait`, not a promise; absent where nobody has measured
- `syncEndpoint` — the endpoint that runs the op while you wait; absent when there is none
USE THIS WHEN:
- Building a tool list or an operation dropdown
- Checking a request''s parameters before submitting it'
operationId: list_5
responses:
'200':
description: OK
headers:
RateLimit-Limit:
description: Requests allowed in the current window
schema:
type: integer
format: int32
RateLimit-Remaining:
description: Requests left in the current window
schema:
type: integer
format: int32
RateLimit-Reset:
description: Seconds until the window resets
schema:
type: integer
format: int32
X-Request-Id:
description: This request's id — echoed from your own `X-Request-Id` when you send one. Identical to `error.requestId` on a failure; quote it when reporting one.
schema:
type: string
content:
'*/*':
schema:
$ref: '#/components/schemas/ApiResponseListOpDefinition'
'429':
description: Rate limited — `error.code` is `rate_limited` and `retryable` is true. `Retry-After` says how many seconds to wait.
headers:
Retry-After:
description: Seconds to wait before retrying
schema:
type: integer
format: int32
RateLimit-Limit:
description: Requests allowed in the current window
schema:
type: integer
format: int32
RateLimit-Remaining:
description: Requests left in the current window
schema:
type: integer
format: int32
RateLimit-Reset:
description: Seconds until the window resets
schema:
type: integer
format: int32
X-Request-Id:
description: This request's id — echoed from your own `X-Request-Id` when you send one. Identical to `error.requestId` on a failure; quote it when reporting one.
schema:
type: string
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
security: []
/api/v1/ops/{op}:
get:
tags:
- Ops
summary: One op's contract
description: 'The catalogue entry of one op, as `GET /api/v1/ops` lists it: its parameters, its price, an example
request and its synchronous endpoint. Answers without a credential.'
operationId: get_3
parameters:
- name: op
in: path
description: The op's name, as `op` in the catalogue
required: true
schema:
type: string
example: remove_bg
responses:
'200':
description: The op's entry
headers:
RateLimit-Limit:
description: Requests allowed in the current window
schema:
type: integer
format: int32
RateLimit-Remaining:
description: Requests left in the current window
schema:
type: integer
format: int32
RateLimit-Reset:
description: Seconds until the window resets
schema:
type: integer
format: int32
X-Request-Id:
description: This request's id — echoed from your own `X-Request-Id` when you send one. Identical to `error.requestId` on a failure; quote it when reporting one.
schema:
type: string
content:
'*/*':
schema:
$ref: '#/components/schemas/ApiResponseOpDefinition'
'404':
description: '`not_found` — no such op; the message lists the ones there are'
headers:
RateLimit-Limit:
description: Requests allowed in the current window
schema:
type: integer
format: int32
RateLimit-Remaining:
description: Requests left in the current window
schema:
type: integer
format: int32
RateLimit-Reset:
description: Seconds until the window resets
schema:
type: integer
format: int32
X-Request-Id:
description: This request's id — echoed from your own `X-Request-Id` when you send one. Identical to `error.requestId` on a failure; quote it when reporting one.
schema:
type: string
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'429':
description: Rate limited — `error.code` is `rate_limited` and `retryable` is true. `Retry-After` says how many seconds to wait.
headers:
Retry-After:
description: Seconds to wait before retrying
schema:
type: integer
format: int32
RateLimit-Limit:
description: Requests allowed in the current window
schema:
type: integer
format: int32
RateLimit-Remaining:
description: Requests left in the current window
schema:
type: integer
format: int32
RateLimit-Reset:
description: Seconds until the window resets
schema:
type: integer
format: int32
X-Request-Id:
description: This request's id — echoed from your own `X-Request-Id` when you send one. Identical to `error.requestId` on a failure; quote it when reporting one.
schema:
type: string
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
security: []
components:
schemas:
ApiError:
type: object
description: Structured error. Present on every 4xx/5xx response, absent on success.
properties:
code:
type: string
description: Machine-readable error code from a closed set
example: invalid_param
message:
type: string
description: Human-readable explanation. Never parse this — parse `code`.
example: 'format must be one of: jpeg, png, webp'
retryable:
type: boolean
description: Whether repeating the same request unchanged could succeed. The whole reason this field exists is that an agent cannot infer it from the status code.
example: false
param:
type: string
description: Which request parameter the error is about, when it is about one
example: format
details:
type: object
additionalProperties: {}
description: Extra machine-readable context, e.g. per-field validation messages
requestId:
type: string
description: 'The id of this request, identical to the `X-Request-Id` response header. Quote it when reporting a failure: it is what finds the request in our logs.'
example: 9f0c1c2e-6f2a-4f6b-9d3e-0a1b2c3d4e5f
required:
- retryable
OpPricing:
type: object
description: 'An op''s price as data: the basis, and the numbers behind it read from the catalogues that own them'
properties:
basis:
type: string
description: '`per_item`: credits per output item at the model''s USD price · `process_quota`: counted against the plan''s processing allowance, and paid from the balance per run past it (`overageCredits`) · `free`'
enum:
- per_item
- process_quota
- free
summary:
type: string
description: The same, in one sentence
defaultModel:
$ref: '#/components/schemas/ImageModelPrice'
description: 'AI ops: the default model''s published price, as `GET /api/pricing/image-models` lists it'
markup:
type: number
format: double
description: 'AI ops: `price = providerPrice × (1 + markup)`'
creditsPerUsd:
type: integer
format: int64
description: 'AI ops: credits per US dollar'
processLimit:
type: object
additionalProperties:
type: integer
format: int32
description: 'Deterministic ops: items per billing period, by plan; -1 is unlimited'
overageCredits:
type: object
additionalProperties:
type: integer
format: int64
description: 'Deterministic ops: credits per run past `processLimit`, paid from the balance — by plan, for the plans that have a limit'
exactPrice:
type: string
description: How to learn the exact price of one request before spending it
OpDefinition:
type: object
description: 'One atomic op: what it does, how to call it, what it costs and whether it can run while you wait'
properties:
op:
type: string
description: The op's name — what `op` takes on a job, a preset step or a synchronous call
example: remove_bg
kind:
type: string
description: '`ai` runs a model and is charged credits per item; `deterministic` is an image pipeline, counted against the processing allowance; `sync` is answered by an endpoint, never a job'
enum:
- ai
- deterministic
- sync
produces:
type: string
description: 'What a step of this op hands to the next: an image, or a JSON answer — which only a last step can produce'
enum:
- image
- json
description:
type: string
description: What the op does, in one sentence
jobType:
type: string
description: The job type the op runs as; absent for a sync op
endpoint:
type: string
description: 'A sync op: the endpoint that answers it instead of a job'
requiresAssets:
type: boolean
description: Whether a job of this op needs `assetIds`
requiresPrompt:
type: boolean
description: Whether a job of this op needs a `prompt`
defaultModel:
type: string
description: 'AI ops: the model a request that names none runs on; its price is `pricing.defaultModel`'
defaultPrompt:
type: string
description: '`analyze` only: what it asks when neither the request nor its preset gives a prompt'
defaultSchema:
type: object
additionalProperties: {}
description: '`analyze` only: the answer''s JSON Schema when neither the request nor its preset gives `parameters.schema`'
params:
type: object
additionalProperties:
type: object
additionalProperties: {}
description: 'The parameter contract: name → `{type, enum?, min?, max?, required?, default?, description}` — `enum`, `min` / `max` and `required` are what the op enforces. A deterministic op refuses any other parameter with `400 invalid_param`'
pricing:
$ref: '#/components/schemas/OpPricing'
description: How the op is priced, as data; the exact price of one request is the dry run
syncEndpoint:
type: string
description: 'The endpoint that runs the op while you wait — bytes in, bytes out, nothing stored — or absent when it has none. AI ops never do: a model call needs a job to own its retry and refund'
maxInputEdge:
type: integer
format: int32
description: 'AI ops: the longest edge, in pixels, an input image is scaled down to before the default model sees it; another model''s is its `maxInputEdge` in GET /api/v1/ai-models'
modelCategory:
type: string
description: 'AI ops: the category a model must carry (GET /api/v1/ai-models → `categories`) to run this op; any other `model` is `400 invalid_param`'
example:
type: object
additionalProperties: {}
description: One complete `POST /api/v1/jobs` body that runs this op, with a placeholder asset id — it has passed the same validator a submit does. Absent for a sync op
typicalSeconds:
type: integer
format: int32
description: How long one item usually takes as a job, submit to terminal, on the default model — a measured median for sizing `wait`, not a promise. Absent where nobody has measured it, which never means fast
required:
- requiresAssets
- requiresPrompt
ApiResponseListOpDefinition:
type: object
description: Standard API response wrapper containing status, message, data and timestamp
properties:
success:
type: boolean
description: Indicates if the request was successful
example: true
message:
type: string
description: Human-readable message about the response
example: Operation completed successfully
data:
type: array
description: The response data payload
items:
$ref: '#/components/schemas/OpDefinition'
timestamp:
type: string
format: date-time
description: Timestamp when the response was generated
example: '2024-01-15T10:30:45.123Z'
error:
$ref: '#/components/schemas/ApiError'
description: Structured error; present exactly when the request failed
meta:
$ref: '#/components/schemas/PageMeta'
description: Pagination metadata for list responses
required:
- data
- success
ErrorResponse:
type: object
description: 'The answer to every failed request: `error` says what went wrong and whether to retry, and nothing else rides beside it — no `data`, no `meta`.'
properties:
success:
type: boolean
description: Always `false` on a failure
error:
$ref: '#/components/schemas/ApiError'
timestamp:
type: string
format: date-time
description: When the response was generated
required:
- error
- success
ImageModelPrice:
type: object
description: 'A model''s published price: ours, after markup, beside the provider''s own list price'
properties:
id:
type: string
description: The model's id, as `model` names it
name:
type: string
description: The model's name
series:
type: string
description: The provider family
description:
type: string
description: What the model does
categories:
type: array
description: What the model is sold for — an op's `modelCategory` is one of these
items:
type: string
priceFrom:
type: string
description: Our lowest price per image, in USD, as a decimal string
example: '0.0024'
priceRange:
type: string
description: Our price per image, or its range, written for a person
example: $0.006 - $0.253
providerPrice:
type: string
description: The provider's own list price per image, in USD, before our markup — the cheapest end when the price has a range
providerPriceRange:
type: string
description: The provider's list price across the same span as `priceRange`, for a model whose cost depends on a parameter; absent when one number covers it
required:
- id
PageMeta:
type: object
properties:
total:
type: integer
format: int64
description: 'Rows matching the filter. Page mode only: a cursor request counts nothing'
page:
type: integer
format: int32
description: The page served, from 0. Page mode only
perPage:
type: integer
format: int32
description: Rows per page actually used (out of range is clamped)
hasMore:
type: boolean
description: Whether there is another page
nextCursor:
type:
- string
- 'null'
description: Pass back as `cursor` for the rows after this page; null on the last page. Opaque — send it unchanged, with the same filters
ApiResponseOpDefinition:
type: object
description: Standard API response wrapper containing status, message, data and timestamp
properties:
success:
type: boolean
description: Indicates if the request was successful
example: true
message:
type: string
description: Human-readable message about the response
example: Operation completed successfully
data:
$ref: '#/components/schemas/OpDefinition'
description: The response data payload
timestamp:
type: string
format: date-time
description: Timestamp when the response was generated
example: '2024-01-15T10:30:45.123Z'
error:
$ref: '#/components/schemas/ApiError'
description: Structured error; present exactly when the request failed
meta:
$ref: '#/components/schemas/PageMeta'
description: Pagination metadata for list responses
required:
- data
- success
securitySchemes:
apiKeyAuth:
type: apiKey
description: 'API Key authentication. Format: ''ApiKey <your-api-key>''. Note: Only allowlisted endpoints accept API key authentication.'
name: Authorization
in: header