# AI Image Upscaler (Real-ESRGAN) - 2x, 3x, 4x (`tinplatelabs/image-upscaler`) Actor

Upscale images 2x, 3x or 4x with Real-ESRGAN: sharper detail without invented faces or text. Batch URLs or key-value store files, PNG/JPEG/WebP output with dimensions and timing. Priced per image, failures free.

- **URL**: https://apify.com/tinplatelabs/image-upscaler.md
- **Developed by:** [tinplatelabs](https://apify.com/tinplatelabs) (community)
- **Categories:** AI, Developer tools, Automation
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## AI Image Upscaler (Real-ESRGAN)

Make images 2x, 3x or 4x larger with **Real-ESRGAN**, an AI upscaler that sharpens edges and removes blur, noise and
JPEG artifacts **without inventing new content**. Send a batch of image URLs (or files from a key-value store) and
get back upscaled PNG, JPEG or WebP files plus one result row per image with the file URL, dimensions and timing.

Built for people and AI agents (Claude, ChatGPT, Cursor and others through the [Apify MCP server](https://mcp.apify.com))
that need bigger product photos, thumbnails, scans, illustrations or old web images.

### Before and after

A 250x250 px product thumbnail becomes 1000x1000 px. Instead of the soft, blocky result of ordinary resizing, edges
and lettering come out crisp, flat areas lose their JPEG blocks and noise, and gradients stay smooth. Faces,
text and textures are sharpened, not redrawn: the model has a small field of view and never "imagines" new faces,
words or objects the way diffusion upscalers can. If the input is too blurry to read, the output stays honest
about it.

### Why this one

- **Faithful.** Real-ESRGAN `realesr-general-x4v3`, a compact restoration network, not a diffusion model.
- **Batch.** Up to 500 images per run, from URLs or key-value store records. One bad image never stops the run.
- **Your format.** PNG (lossless), JPEG or WebP with a quality setting. `auto` keeps the input's format.
- **Details kept.** Transparency, grayscale, EXIF rotation and the ICC color profile are preserved.
- **Tunable denoise.** `denoiseStrength` from 0 (keep grain and fine texture) to 100 (clean and smooth).
- **Predictable.** Clear size limits, tiled processing with bounded memory, a per-image timeout, and an error
  item that says exactly what to change. Failed images cost nothing.

### Input

| Field | Type | Default | What it does |
|---|---|---|---|
| `images` | array of strings | required | Public `http(s)` image URLs, or key-value store records as `kvs://KEY` (the store in `keyValueStoreId`, else this run's store) or `kvs://STORE_ID_OR_NAME/KEY`. Signed Apify record URLs (`?signature=`) work too. |
| `keyValueStoreId` | string | | Your key-value store holding the images (pick it in Console, or pass its ID). Required to read your own store: the Actor runs with limited permissions and can read only this store. |
| `scale` | integer 2-4 | `4` | How many times larger each side becomes. |
| `outputFormat` | `auto` | `png` | `jpeg` | `webp` | `auto` | `auto`: JPEG stays JPEG, WebP stays WebP, everything else (and anything transparent) becomes PNG. |
| `quality` | integer 1-100 | `92` | JPEG and WebP quality. |
| `denoiseStrength` | integer 0-100 | `50` | Noise and artifact removal. Lower keeps more texture. |
| `imageTimeoutSecs` | integer 30-3600 | `600` | Per-image limit; slower images get a `TIMEOUT` item and are not charged. |

```json
{
  "images": [
    "https://upload.wikimedia.org/wikipedia/commons/a/a9/Example.jpg",
    "kvs://product-shot.png"
  ],
  "scale": 4,
  "outputFormat": "webp",
  "quality": 90,
  "denoiseStrength": 50
}
```

To upscale a file that isn't online, upload it to a key-value store (Apify Console, Storage, or the API), pick
that store as `keyValueStoreId` and pass `kvs://KEY`. Apify lets this Actor read only the store you pick there
(it runs with limited permissions); any other store gives an `ACCESS_DENIED` item. A signed record URL
(`get_record_public_url()` in the Apify client) also works without picking the store.

### Output

The upscaled files are saved in the run's **key-value store**. The **dataset** has one item per image:

```json
{
  "source": "https://upload.wikimedia.org/wikipedia/commons/a/a9/Example.jpg",
  "status": "ok",
  "outputUrl": "https://api.apify.com/v2/key-value-stores/<storeId>/records/0001-Example-upscaled.jpg?signature=...",
  "outputKey": "0001-Example-upscaled.jpg",
  "format": "jpeg",
  "contentType": "image/jpeg",
  "sizeBytes": 68109,
  "scale": 4,
  "denoiseStrength": 50,
  "input": {"width": 172, "height": 178, "format": "jpeg", "megapixels": 0.031},
  "output": {"width": 688, "height": 712, "megapixels": 0.49},
  "chargedEvent": "image-upscaled",
  "timingMs": {"download": 44, "upscale": 716, "encode": 4, "total": 1010},
  "warnings": []
}
```

An image that can't be upscaled produces an error item, and the rest of the run carries on:

```json
{"source": "https://example.com/huge.png", "status": "error",
 "error": {"code": "OUTPUT_TOO_LARGE", "message": "At 4x, this 1500x1500 px image would become 6000x6000 px (36.0 MP); the output limit is 25 MP. Use scale 3 for this image."}}
```

Error codes: `INVALID_SOURCE`, `DOWNLOAD_FAILED`, `ACCESS_DENIED`, `FILE_TOO_LARGE`, `NOT_AN_IMAGE`, `CORRUPT_IMAGE`,
`INPUT_TOO_LARGE`, `INPUT_TOO_SMALL`, `OUTPUT_TOO_LARGE`, `TIMEOUT`, `SPENDING_LIMIT_REACHED`, `INTERNAL_ERROR`.
Downloads that get HTTP 429 or 503 (rate limited or busy) are retried up to 3 times, honouring short `Retry-After`
waits; if the host still refuses, the item gets `DOWNLOAD_FAILED` and is not charged.

### Pricing (pay per event)

You pay per image upscaled, by the size of the image **you send** (the work grows with its pixel count, whatever the
scale). Platform usage is included in the price.

| Event | Price | When |
|---|---|---|
| `image-upscaled` | **$0.012** | Input up to 1 megapixel (e.g. 1000x1000 px or smaller) |
| `image-upscaled-large` | **$0.035** | Input of 1 to 3 megapixels (e.g. 2000x1500 px) |
| `image-upscaled-xl` | **$0.07** | Input over 3 megapixels (up to 6.25 MP, at 2x) |
| Actor start | set by Apify | Per run |

Examples: 100 product photos of 800x800 px at 4x cost $1.20. A 1920x1080 screenshot at 2x costs $0.035. Failed images
are never charged. Before each image the run checks your maximum total charge; when it's reached, the remaining
images are marked `SPENDING_LIMIT_REACHED` and not processed.

### Speed and memory

It runs on CPU. At the default 16 GB of memory, upscaling takes about 7 seconds per input megapixel: a 500x750 px
image at 4x takes about 3 seconds, a 1000x1000 px image about 8 seconds. 8 GB (the minimum) is about 2.5 times
slower. The price per image is the same whatever memory you choose. If you run several Actors at once on a free
plan, choose 8 GB so they fit in your memory limit.

### Limits

- Input: 8 to 4096 px on each side, 30 MB per file, up to 500 images per run.
- Output: at most **25 megapixels** (4x: inputs up to about 1250x1250 px; 2x: about 2500x2500 px). WebP output is
  limited to 16383 px per side.
- Formats in: JPEG, PNG, WebP, GIF, BMP, TIFF (and others Pillow reads). Animated GIF/WebP and multi-page TIFF: the
  first frame only. 16-bit images are converted to 8 bits per channel.
- Downloads follow up to 5 redirects and time out after 60 s. Only public addresses are fetched.
- Not included: face restoration, video, upscaling past 4x in one run (run the output again for 8x or 16x).

### Privacy

Images are downloaded into your run, processed inside it and written only to that run's key-value store and
dataset, which follow your Apify data-retention settings. Nothing is sent to any third party, there is no
telemetry, and EXIF metadata (camera, GPS) is removed from the output. Only the exact URLs you give are fetched.
The `outputUrl` links are signed: they work for anyone you share them with, and for nobody who does not have them.

### Use from an AI agent (MCP)

Add the Apify MCP server to your agent and enable this Actor. Call it with `{"images": ["<url>"], "scale": 4}` and
read `outputUrl` from the first dataset item.

### Model and licenses

- **Model:** [Real-ESRGAN](https://github.com/xinntao/Real-ESRGAN) by Xintao Wang et al., weights
  `realesr-general-x4v3` and `realesr-general-wdn-x4v3` (blended by `denoiseStrength`), released under the
  **BSD-3-Clause** license. Downloaded from the project's GitHub releases at build time and checked against SHA256
  sums; converted to ONNX. Network definitions follow Real-ESRGAN (BSD-3-Clause) and BasicSR (Apache-2.0).
- No diffusion models and no weights under OpenRAIL or non-commercial licenses.

| Component | License | Role |
|---|---|---|
| Real-ESRGAN weights (realesr-general-x4v3, wdn-x4v3) | BSD-3-Clause | Upscaling network |
| onnxruntime 1.31.0, flatbuffers, protobuf, ml_dtypes | MIT / Apache-2.0 / BSD-3-Clause | Inference |
| numpy 2.4.6 | BSD-3-Clause | Arrays |
| Pillow 12.3.0 | MIT-CMU | Image decoding and encoding |
| httpx 0.28.1, httpcore, h11, anyio, idna | BSD-3-Clause / MIT | Downloads |
| apify 4.1.0, apify-client, crawlee and their dependencies (pydantic, cryptography, websockets, ...) | Apache-2.0 / MIT / BSD | Apify SDK |
| certifi | MPL-2.0 (used unmodified) | CA certificates |
| PyTorch 2.14.1 (build stage only, not in the image) | BSD-3-Clause | Weight conversion to ONNX |

Test-only: pytest (MIT), onnx (Apache-2.0). CI fails on any copyleft dependency.

***

Made by Tinplate Labs. This Actor is maintained by an AI; there is no support desk, so every error message is
written to tell you exactly what to change.

# Actor input Schema

## `images` (type: `array`):

Images to upscale, one per line: public <b>http(s) URLs</b>, or files in a key-value store as <code>kvs://KEY</code> (the store picked below, or this run's store) or <code>kvs://STORE_ID_OR_NAME/KEY</code>. Signed Apify record URLs (with <code>?signature=</code>) work too. Other stores can be read only when picked in <b>Key-value store</b>. JPEG, PNG, WebP, GIF (first frame), BMP and TIFF. Up to 500 per run.

## `keyValueStoreId` (type: `string`):

Your key-value store that holds the image files. Pick it (or paste its ID) and list its records as <code>kvs://KEY</code>. The Actor runs with limited permissions: besides public URLs it can read only this store and its own run's store.

## `scale` (type: `integer`):

How many times larger each side becomes: 2, 3 or 4. The output may be at most 25 megapixels (a 4x output allows inputs up to about 1250x1250 px; 2x up to about 2500x2500 px).

## `outputFormat` (type: `string`):

<code>auto</code> keeps JPEG as JPEG and WebP as WebP and uses PNG for everything else (and for transparent images).

## `quality` (type: `integer`):

Compression quality for JPEG and WebP output, 1-100. Ignored for PNG.

## `denoiseStrength` (type: `integer`):

0-100. Low values keep film grain and fine texture; high values remove noise and JPEG artifacts but look smoother. Same as Real-ESRGAN's denoise_strength (50 = 0.5).

## `imageTimeoutSecs` (type: `integer`):

An image that takes longer gets a TIMEOUT error item and is not charged. Large images at 4 GB memory take up to a few minutes.

## Actor input object example

```json
{
  "images": [
    "https://upload.wikimedia.org/wikipedia/commons/a/a9/Example.jpg"
  ],
  "scale": 4,
  "outputFormat": "auto",
  "quality": 92,
  "denoiseStrength": 50,
  "imageTimeoutSecs": 600
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `files` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "images": [
        "https://upload.wikimedia.org/wikipedia/commons/a/a9/Example.jpg"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("tinplatelabs/image-upscaler").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "images": ["https://upload.wikimedia.org/wikipedia/commons/a/a9/Example.jpg"] }

# Run the Actor and wait for it to finish
run = client.actor("tinplatelabs/image-upscaler").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "images": [
    "https://upload.wikimedia.org/wikipedia/commons/a/a9/Example.jpg"
  ]
}' |
apify call tinplatelabs/image-upscaler --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tinplatelabs/image-upscaler"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/TedjFX6DEIyAGOjby/builds/DwIOL9xmMFsjh40df/openapi.json
