Upscaling images
An upscale re-renders one of your images at a larger size, 2K or 4K, with an AI model. The result is a
new asset; the original is never changed. It is a faithful re-render, not a pixel-exact resample, so the
output can differ in fine detail. Keep it or delete it — a delivered render is charged either way.
Like a run, an upscale spends credits and is asynchronous: POST /upscales (createImageUpscale) answers
202 at once, and you poll GET /upscales/{upscaleId} (getImageUpscale) until it is final.
Permissions
Creating needs a key with the runs:write scope and a plan that includes the Create API; reading needs
runs:read. These are the same scopes and rate limits as runs. A size your plan does not include is
refused with 403 IMAGE_UPSCALE_NOT_ENTITLED before anything is read or charged.
1. Create an upscale
Idempotency-Key is required (see Idempotency). Name the image by an asset you own, or by
one of a product's own images; a URL is never accepted.
curl -X POST https://api.mocart.io/api/public/v1/upscales \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "source": { "assetId": "asset_123" }, "target": "2K" }'
| Field | Meaning |
|---|---|
source | { "assetId": "…" } (a still image of yours, from any source) or { "productId": "…", "image": "primary" }; image may also be gallery, with an optional index (default 0). |
target | 2K or 4K. It must be larger than the source: a source whose long edge is already 2048 px (2K) or 4096 px (4K) is refused. |
The 202 carries the upscale and a Location header:
{
"object": "image_upscale",
"upscaleId": "3f6c…",
"status": "QUEUED",
"source": { "assetId": "asset_123" },
"target": "2K",
"assetId": "3f6c…",
"output": null,
"error": null,
"createdAt": "2026-10-04T10:00:00.000Z",
"completedAt": null
}
A retry with the same key and body returns the same upscale and is never charged twice. The same key with a
different body answers 409 IDEMPOTENCY_KEY_REUSED.
2. Poll
status is QUEUED, PROCESSING, SUCCEEDED or FAILED; the last two are final. Poll every few seconds.
On SUCCEEDED, the new image is the asset assetId (always equal to upscaleId): read it with
GET /assets/{assetId}. output carries its pixel size once the asset has been measured, and can be null for
a short while after success. That asset carries a derivation:
"derivation": { "kind": "ai_rerender", "operation": "image_upscale", "parentAssetId": "asset_123", "rootAssetId": "asset_123" }
derivation appears only on an asset made this way. parentAssetId and rootAssetId are null when the source was a
product image rather than an asset.
On FAILED, error.code says why, and nothing is charged:
error.code | Meaning | What to do |
|---|---|---|
SAFETY_REFUSED | The content policy refused the image. | Do not retry the same image. |
TRY_AGAIN | A temporary failure. | Send the request again (new key). |
RENDER_FAILED | The render could not be completed. | Retrying is unlikely to help. |
CANCELLED | The upscale was cancelled. | — |
Refusals
Decided before anything is held, so the Idempotency-Key is not used up:
| Status | Code | Meaning |
|---|---|---|
402 | INSUFFICIENT_CREDITS | The account cannot cover the upscale. |
403 | IMAGE_UPSCALE_NOT_ENTITLED | The plan does not include this size. |
404 | ASSET_NOT_FOUND / PRODUCT_NOT_FOUND | Not yours, deleted, not a still image, or an empty slot — indistinguishable. |
409 | IMAGE_UPSCALE_IN_PROGRESS | This image is already being upscaled to this size. |
409 | IMAGE_UPSCALE_UNPRICED | The size has no published price yet. |
422 | IMAGE_UPSCALE_TARGET_NOT_LARGER | The image is already as large as the target; detail states its size. |
422 | IMAGE_UPSCALE_SOURCE_UNREADABLE | The image could not be read. |
422 | VALIDATION_ERROR | A malformed body, an unknown field, or target other than 2K / 4K. |
Another account's upscaleId reads 404 IMAGE_UPSCALE_NOT_FOUND, the same answer as an unknown one.