---
title: Upscale an image
url: https://base_url.placeholder/docs/ops/upscale
group: Concepts
---

# 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](https://base_url.placeholder/docs/ops), one [job handle](https://base_url.placeholder/docs/jobs), one price the [dry run](https://base_url.placeholder/docs/jobs#price) 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

```js
// pnpm add imagestep
const job = await client.ops.upscale("ast_1a2b", { parameters: { scaleFactor: 2 }, wait: true });
const [out] = await client.jobs.outputs(job);
```

#### Python

```python
# pip install imagestep
job = client.ops.upscale("ast_1a2b", parameters={"scaleFactor": 2}, wait=True)
[out] = client.jobs.outputs(job)
```

#### CLI

```sh
imagestep jobs submit --op upscale --asset-ids ast_1a2b --params '{"scaleFactor":2}' --wait
```

#### curl

```sh
# "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

```text
# tool call
transform  {"op":"upscale","asset_ids":["ast_1a2b"],"parameters":{"scaleFactor":2}}
```

#### n8n

```json
{
  "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](https://base_url.placeholder/docs/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](https://base_url.placeholder/docs/sdk), [CLI](https://base_url.placeholder/docs/cli), [MCP](https://base_url.placeholder/docs/mcp), [REST](https://base_url.placeholder/docs/api).

## Parameters, model and price

The entry as the catalogue stood when this page was built; `GET https://api.imagestep.dev/api/v1/ops/upscale` has it live — the same entry the SDKs, the MCP server and the n8n node enumerate. 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 edge

Increase 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](https://base_url.placeholder/docs/jobs#price), 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

```js
// 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

```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

```sh
# 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 json
```

Over REST these are the three requests of [jobs](https://base_url.placeholder/docs/jobs#price), 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](https://base_url.placeholder/docs/webhooks) earn their keep: submit, return, and let `job.completed` wake the rest of your flow. From an agent, the [MCP server](https://base_url.placeholder/docs/mcp#waiting) 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](https://base_url.placeholder/docs/errors#items) 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.
