Upscale an image
upscale raises an image's resolution — 2× or 4× on the default model — and regenerates the detail while it does, instead of stretching the pixels it already has. One op of the catalogue, one job handle, one price the dry run will tell you in advance.
What it does
The model reads the image and writes a larger one, inventing texture that interpolation cannot: an edge stays an edge, a fabric keeps a weave. parameters.scaleFactor is how much bigger: 2 by default, or 4. The result is a new asset; the original is untouched.
Reach for it when a program is handed something too small to use — a marketplace thumbnail that has to become a hero image, an archive scan, a frame someone screenshotted. Do not reach for it to make a file merely larger on disk; that is resize, it is deterministic, and on a paid plan it is free. And upscaling does not undo compression artefacts: it enlarges them too.
Call it
One op, six surfaces, one vocabulary: the op name below is the same string everywhere — the SDKs' named helpers only spell it in their language's case, ops.upscale in JavaScript — because every surface enumerates GET /api/v1/ops instead of carrying its own list. The request is the catalogue's own example for this op, so it is the one the service has checked.
JavaScript
// pnpm add imagestep
const job = await client.ops.upscale("ast_1a2b", { parameters: { scaleFactor: 2 }, wait: true });
const [out] = await client.jobs.outputs(job);Python
# pip install imagestep
job = client.ops.upscale("ast_1a2b", parameters={"scaleFactor": 2}, wait=True)
[out] = client.jobs.outputs(job)CLI
imagestep jobs submit --op upscale --asset-ids ast_1a2b --params '{"scaleFactor":2}' --waitcurl
# "wait" holds the response until the job is done (60 s at most): 200 with the finished job,
# or 202 with the handle — then GET /api/v1/jobs/<id>?wait=30 waits again
curl -sS -X POST https://api.imagestep.dev/api/v1/jobs \
-H "Authorization: ApiKey $IMAGESTEP_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"op":"upscale","assetIds":["ast_1a2b"],"parameters":{"scaleFactor":2},"wait":30}'MCP
# tool call
transform {"op":"upscale","asset_ids":["ast_1a2b"],"parameters":{"scaleFactor":2}}n8n
{
"nodes": [
{
"parameters": {
"resource": "op",
"operation": "run",
"op": "upscale",
"inputMode": "assetIds",
"assetIds": "ast_1a2b",
"parameters": "{\"scaleFactor\":2}"
},
"name": "ImageStep",
"type": "n8n-nodes-imagestep.imageStep",
"typeVersion": 1,
"position": [
0,
0
]
}
],
"connections": {}
}The n8n tab is a workflow to paste onto the canvas: the upscale entry of the node's Op dropdown, which is filled from the same catalogue. Full references: SDKs, CLI, MCP, REST.
Parameters, model and price
Read live from GET https://api.imagestep.dev/api/v1/ops/upscale — the same entry the SDKs, the MCP server and the n8n node enumerate, so this table cannot fall behind the API. The rows are fields of the request body. parameters goes to the model: its keys are the model's own, listed under parameters for each model in GET /api/v1/ai-models, and a key the model does not declare, or a value outside its options or bounds, is 400 invalid_param naming it.
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.
That is how it is priced. What one request costs — another model, a bigger scale factor, forty images — is the dry run, which prices the exact body you are about to send without creating anything.
In a workflow
Upscaling is the expensive step, so the two patterns worth knowing are both about spending it deliberately: price the batch before you submit it, and re-encode afterwards so the extra pixels do not become an extra megabyte on every page.
JavaScript
// Price it first — nothing is created, nothing is charged.
const quote = await client.ops.upscale(assetIds, { parameters: { scaleFactor: 4 }, dryRun: true });
if (!quote.sufficientCredit) throw new Error(`needs ${quote.estimatedCredits}, have ${quote.creditBalance}`);
// Then run it, and hand the output to a deterministic step — free on a paid plan.
const job = await client.ops.upscale(assetIds, { parameters: { scaleFactor: 4 }, wait: true });
const outputs = await client.jobs.outputs(job);
await client.ops.convert(outputs.map((a) => a.id), { format: "webp", quality: 82 }, { wait: true });Python
# Price it first — nothing is created, nothing is charged.
quote = client.ops.upscale(asset_ids, parameters={"scaleFactor": 4}, dry_run=True)
if not quote["sufficientCredit"]:
raise RuntimeError(f"needs {quote['estimatedCredits']}, have {quote['creditBalance']}")
# Then run it, and hand the output to a deterministic step — free on a paid plan.
job = client.ops.upscale(asset_ids, parameters={"scaleFactor": 4}, wait=True)
outputs = client.jobs.outputs(job)
client.ops.convert([a["id"] for a in outputs], {"format": "webp", "quality": 82}, wait=True)CLI
# Price it first — nothing is created, nothing is charged.
imagestep jobs estimate --op upscale --asset-ids "$ID" --params '{"scaleFactor":4}' -o json | jq -e '.sufficientCredit' > /dev/null || exit 1
# Then run it, and hand the output to a deterministic step — free on a paid plan.
JOB=$(imagestep jobs submit --op upscale --asset-ids "$ID" --params '{"scaleFactor":4}' --wait -o json | jq -r '.id')
OUT=$(imagestep jobs outputs "$JOB" -o json | jq -r '.[0].assetId')
imagestep jobs submit --op convert --asset-ids "$OUT" --params '{"format":"webp","quality":82}' --wait -o jsonOver REST these are the three requests of jobs, one after the other; an agent makes them one tool call at a time — transform with dry_run, then without.
A long batch is where webhooks earn their keep: submit, return, and let job.completed wake the rest of your flow. From an agent, the MCP server hands back the job handle when a wait runs out, so the conversation keeps its place instead of losing the work.
Limits
The default model is handed at most 1024 px on the long edge — a larger input is scaled down first — so its largest result is 2048 px at 2× and 4096 px at 4×. An input already past that edge comes back less than the factor larger, and running upscale on an output gains nothing. It takes scaleFactor 2 or 4 and nothing between: any other value is 400 invalid_param on parameters.scaleFactor, before anything is spent. 4× is the slowest thing this API does; expect a job you wait on, not a call you block a request handler with. Text and faces are where upscalers hallucinate most; a sign in the background may come back saying something else. Items that fail inside a job carry their own code and retryable flag and are not charged.
FAQ
- How is this different from resizing up?
- resize interpolates: a 2× enlargement has the same detail spread over four times the pixels, so it looks soft. upscale runs a model that invents plausible detail, which is why it costs credits and resize does not.
- How big can it go?
- The default model enlarges 2× or 4× (scaleFactor; any other value is 400 invalid_param), from an input it is handed at most the catalogue's maxInputEdge on the long edge — so that edge times the factor is the largest result, whatever the size you started with. Upscaling the output again gains nothing, because the second run's input is scaled back down first. For more, pick an upscaler that goes further (its factors are under parameters in GET /api/v1/ai-models), and check the price: several providers bill by output megapixel, so 4× is not twice the price of 2×.
- Why is the price a range?
- Because the provider's is. The dry run prices the exact scale factor and model you are about to send, so the number you see is the number you are charged.
- Can it fix a face?
- Not reliably — an upscaler enlarges what is there. For portraits use restore_face, which is trained on faces, and upscale afterwards if you still need the pixels.
- Can I upscale a whole collection in one call?
- Yes. List the collection, then one job takes every asset id in assetIds; items settle one by one, a failed item is refunded on its own, and job.completed fires once at the end.