# Image Upscale ×2 / ×4 — CPU super-resolution (`hermeskd/image-upscale`) Actor

Image URLs in, each enlarged 2x or 4x by the Real-ESRGAN super-resolution model, as PNG, JPEG or WebP. Sharper than resizing; for graphics, screenshots, text and product shots. Pay per megapixel.

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

## Pricing

from $16.00 / 1,000 input megapixel upscaleds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

Give it image URLs; it returns each image enlarged two or four times with Real-ESRGAN, a super-resolution model that adds plausible detail, as a PNG, JPEG or WebP file in the run's key-value store. The result is sharper than ordinary resizing, but on photographs it is less faithful to the original pixels than plain bicubic resizing: measured against known originals, it beats bicubic on graphics, screenshots and text and loses to it on photos (the numbers are under Limits). Use it for graphics, screenshots, text, icons and small product shots; do not use it on photos where faithfulness matters — evidence, medical or forensic images, anything measured. It does not restore faces and never removes watermarks or provenance labels. The model (`realesr-general-x4v3`) runs on CPU inside the Actor, so nothing is sent to a third-party service; each record names the stored file and a URL to it, and the price is per started megapixel of input — an ordinary web image under one megapixel costs $0.02.

### Call it from an agent

Through Apify's MCP server (`https://mcp.apify.com`), tool `call-actor`:

```json
{"actor": "hermeskd/image-upscale", "input": {"urls": ["https://raw.githubusercontent.com/python-pillow/Pillow/main/Tests/images/flower.jpg"], "scale": 2}, "callOptions": {"maxTotalChargeUsd": 0.05, "timeout": 1800}, "waitSecs": 30}
```

The call returns the run's status and its `defaultDatasetId`. Read the records with `get-dataset-items`:

```json
{"datasetId": "<defaultDatasetId from the call>", "limit": 50}
```

Each record's `outputUrl` is the enlarged image itself (also readable with `get-key-value-store-record` and the record's `key`). As a dedicated tool, add `https://mcp.apify.com?tools=hermeskd/image-upscale` to your MCP client. An image under one megapixel finishes inside the 30-second wait at the default memory; a larger one can take longer — call again with `waitSecs` up to 45, or read the run later. `maxTotalChargeUsd` caps what the run can charge; when it is reached the remaining URLs come back as `charge_limit_reached` records and are not charged.

### Input

| Field | Type | Required | Default | Meaning |
|---|---|---|---|---|
| `urls` | array of strings | yes | — | Image URLs (PNG, JPEG, WebP, BMP, TIFF, GIF), http or https, 1–20 per run, up to 20 MB each. |
| `scale` | integer, 2 or 4 | no | 2 | Enlargement factor; each side of the image is multiplied by it. 3 is refused as `invalid_input`. |
| `format` | enum `png` · `jpeg` · `webp` | no | `png` | Output format: `png` lossless with transparency; `jpeg` quality 92, transparency flattened onto white; `webp` quality 90 with transparency, at most 16,383 pixels on a side. |
| `maxInputMegapixels` | integer | no | 2 | Images with more pixels than this (in millions, width × height) are refused as `too_large`, uncharged, before they are decoded; 1–4. |

Every limit above is enforced by the input schema and again in code.

### Output

One record per input URL, and one image file per successful record in the run's default key-value store.

| Field | Meaning |
|---|---|
| `url` | The input URL. |
| `ok` | `true` when the image was enlarged and stored; `false` with `error`. |
| `inputWidth` | Input width in pixels, after EXIF orientation. |
| `inputHeight` | Input height in pixels, after EXIF orientation. |
| `outputWidth` | Output width in pixels: input width × `scale`. |
| `outputHeight` | Output height in pixels: input height × `scale`. |
| `megapixelsCharged` | Started input megapixels charged: width × height / 1,000,000 rounded up, at least 1. |
| `key` | Key of the output image in the run's default key-value store. |
| `outputUrl` | The output image's key-value store record URL (API). |
| `error` | `{code, message}` when `ok` is `false`. |

Example record for the call above, as this Actor's code returned it in a local run on 2026-10-16 (the 960 × 720 PNG was 803,103 bytes; `outputUrl` is shown in its platform form, the store id written as `STORE_ID`):

```json
{
  "url": "https://raw.githubusercontent.com/python-pillow/Pillow/main/Tests/images/flower.jpg",
  "ok": true,
  "inputWidth": 480,
  "inputHeight": 360,
  "outputWidth": 960,
  "outputHeight": 720,
  "megapixelsCharged": 1,
  "key": "upscaled-000-428e5ec346.png",
  "outputUrl": "https://api.apify.com/v2/key-value-stores/STORE_ID/records/upscaled-000-428e5ec346.png"
}
```

### Events and prices

Pay per event; platform usage is included in the price, never passed through.

| Event | Title | Charged when | FREE / BRONZE | SILVER | GOLD / PLATINUM / DIAMOND |
|---|---|---|---|---|---|
| `megapixel-upscaled` | Input megapixel upscaled | One started megapixel of input image upscaled. | $0.02 | $0.018 | $0.016 |

The count is the input's started megapixels — width × height / 1,000,000 rounded up, at least 1 — whatever the `scale` and `format`, and it is charged only after the file is stored and its record written. Apify also charges its standard `apify-actor-start` event: $0.00005 per GB of run memory (4 events, $0.0002, at the default 4,096 MB). The minimum maximum-charge a caller can set is $0.05.

Worked example on the FREE tier: the 480 × 360 photo above (0.17 megapixels) is 1 started megapixel, $0.02, plus $0.0002 for the start, $0.0202 in all. Ten 1,600 × 1,200 photos (1.92 megapixels each, 2 started) cost 10 × 2 × $0.02 = $0.40 plus the start. Nothing is charged for a failed image.

### Limits and run time

- 1–20 URLs per run; files up to 20 MB each; images up to `maxInputMegapixels` (2 by default, at most 4) — the size is read from the file's header and an image over the cap is refused before its pixels are decoded.
- Downloads: files download at 2 MB/s or faster — each download has a budget of 30 s plus 1 s per 2 MB of its declared size (or of the 20 MB cap when the server declares none), and stops when no byte arrives for 30 s; at most 2 requests a second go to one host.
- GitHub release links are refused: github.com's robots.txt says `User-agent: *` `Disallow: /`, which covers HermesBot, so a `github.com/…/releases/download/…` URL comes back `robots_disallowed` without a request. Give the file's raw.githubusercontent.com address, or put it in your own storage (an Apify key-value store works).
- Formats read: PNG, JPEG, WebP, BMP, TIFF (first page) and GIF; an animated GIF or WebP is enlarged from its first frame only. EXIF orientation is applied first. Colour goes through the model; an alpha channel is enlarged with Lanczos; a grayscale image comes back grayscale (WebP: RGB). The ICC colour profile is kept; EXIF, XMP and other metadata are not copied.
- Scale 4 is the model's own output; scale 2 is that output reduced by half with Lanczos (the model's reference method). Images are processed in 512-pixel tiles with 24 pixels of overlap, so memory stays bounded and no seam shows.
- What it is for, measured: the model sharpens edges and removes blur, noise and compression artefacts. It invents plausible detail rather than recovering the true one, so against a known original its pixel accuracy (PSNR, higher is closer) is below plain bicubic resizing on photographs and above it on graphics. Measured on 2026-10-16 on one CPU core: each image reduced by the factor with bicubic, then enlarged back by this Actor (PNG) and by bicubic, PSNR against the original with a 4-pixel border left out. Ten photos are the scikit-image and Pillow sample photos (`flower`, `coffee`, `astronaut`, `rocket`, `chelsea`, `hubble_deep_field` and four Pillow test photos: mountains, an aircraft, grass, a cabin in snow); "JPEG inputs" are the same reductions saved as JPEG quality 75 first; the five graphics are Pillow's `test-card.png`, Pillow's line drawing `uncompressed_rgb.png`, and generated flat shapes, text and a bar chart.

  | Images | ×4: this Actor − bicubic | ×4: beats bicubic | ×2: this Actor − bicubic | ×2: beats bicubic |
  |---|---|---|---|---|
  | 10 photos | −1.44 dB on average (−3.53 to +0.16) | 1 of 10 | −3.33 dB on average (−6.97 to −0.33) | 0 of 10 |
  | 10 photos, JPEG inputs | −1.07 dB (−2.05 to +0.33) | 1 of 10 | −2.06 dB (−4.36 to +0.11) | 2 of 10 |
  | 5 graphics and text | +1.16 dB (−2.00 to +2.55) | 4 of 5 | +1.97 dB (−0.81 to +3.25) | 4 of 5 |

  On the test card, the shapes and the chart it is 2.0 to 2.9 dB closer to the original than bicubic at either factor; text gains 3.3 dB at ×2, but text reduced ×4 to about 4 pixels high is past reading and the model makes it worse (−2.0 dB); the thin-line drawing is about even (+0.9 / −0.8 dB). Recommended for graphics, screenshots, text, icons and small product shots. Not for photos where faithfulness to the original pixels matters (evidence, medical, forensic, measurement): use plain resizing there.
- Typical run time: the model needs about 25 seconds per input megapixel on one CPU core (the default 4,096 MB is one core; 8,192 MB is two and roughly halves it), plus a few seconds for the download, the file and the start: the 480 × 360 example took 6 seconds of work on one core when this release was measured (Apify's cores may be slower). The default run timeout is 1,800 seconds; an image that would not finish in the run's remaining time is answered `timeout`.
- Memory: 2,048 MB is enough up to 2 megapixels of input; a 4-megapixel input enlarged ×4 to WebP needs up to 1.8 GB, so use 4,096 MB or more for it. The largest output, a 4-megapixel input at ×4, is 64 megapixels.

### Errors

Errors are records with `ok: false` and `error.code`; none is charged.

| `error.code` | Meaning |
|---|---|
| `download_failed` | The URL did not return the file (status, network error, a stall of 30 s, or slower than its download budget). |
| `not_image` | Not a supported image (PNG, JPEG, WebP, BMP, TIFF, GIF), an empty body, or a damaged file that cannot be decoded. |
| `too_large` | Over `maxInputMegapixels`, over 20 MB, or an output wider or taller than the format holds (WebP 16,383 pixels, JPEG 65,500). |
| `robots_disallowed` | robots.txt forbids fetching the URL. |
| `blocked_host` | The host is on the deny-list, or resolves to a private or local address. |
| `timeout` | The image did not finish within the run's time budget. |
| `internal_error` | Unexpected failure; message carries the class. |
| `charge_limit_reached` | The run's maximum total charge cannot pay for this image's megapixels; it was not processed. |
| `invalid_input` | The run input passed the input schema but cannot be used (for example `scale` 3, or no URL at all); the message names the field. One record per input given, or one with `url: null` when none was given; nothing is processed. |

The run fails only when every input failed with an Actor-side code (`internal_error`, `timeout`), so a broken Actor is visible. Every other run succeeds — including one where nothing was enlarged because of the caller's own inputs (bad URL, robots, deny-list, not an image, too large, invalid input, the charge limit), or where those and Actor-side failures are mixed.

### Sources and terms

The source is the caller's image. Process only images you have the right to process; the Actor does not remove watermarks, signatures or provenance labels and does not restore or identify faces. Fetching follows the Hermes fetch policy: robots.txt is obeyed for the user-agent `HermesBot` (and `*`), and a disallowed URL is refused without a request; platforms whose terms forbid automated access, and people-search sites, are on a deny-list and refused without a request; private and local addresses are refused, after DNS and on every redirect; the user-agent is `HermesBot/1.0 (+https://apify.com/hermeskd)`; no login, cookies, credentials, CAPTCHA solving, evasion or proxies are used. The enlarged image is returned to the caller in the run's own key-value store.

The model is Real-ESRGAN `realesr-general-x4v3` (release v0.2.5.0 of the Real-ESRGAN project, BSD-3-Clause licence, which allows commercial use), converted to ONNX when the Actor's image is built and run with onnxruntime (MIT licence); it is never downloaded at run time.

### Not affiliated

This Actor is not affiliated with the Real-ESRGAN project or its authors, with Microsoft (onnxruntime), with the Pillow project or with scikit-image, whose public sample images the examples use. Real-ESRGAN, onnxruntime and Pillow are used under their open-source licences.

### Related Hermes Actors

- `pdf-to-markdown` — PDF documents to Markdown, with OCR for scanned pages.
- `document-to-markdown` — Word, PowerPoint, Excel and other office files to Markdown.
- `image-to-text` — the text in an image, by OCR.
- `audio-to-transcript` — speech to text.
- `video-frames` — frames and metadata from a video.
- `url-to-markdown` — a web page to Markdown.
- `audio-video-convert` — audio and video format conversion.

### Changelog

- 1.0 (2026-10-16) — first release: ×2 and ×4 super-resolution with Real-ESRGAN `realesr-general-x4v3` on CPU, PNG, JPEG or WebP out, transparency and grayscale kept, charged per started input megapixel.

# Actor input Schema

## `urls` (type: `array`):

Image URLs (PNG, JPEG, WebP, BMP, TIFF, GIF), http or https, 1–20 per run, up to 20 MB each. Each URL becomes one record and one output file.

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

Enlargement factor: 2 or 4 (each side of the image is multiplied by it). 3 is refused as invalid_input.

## `format` (type: `string`):

png (lossless, keeps transparency), jpeg (quality 92, transparency flattened onto white) or webp (quality 90, keeps transparency; at most 16,383 pixels on a side).

## `maxInputMegapixels` (type: `integer`):

Images with more pixels than this (in millions, width x height) are refused as too_large, uncharged, before they are decoded; 1–4.

## Actor input object example

```json
{
  "urls": [
    "https://raw.githubusercontent.com/python-pillow/Pillow/main/Tests/images/flower.jpg"
  ],
  "scale": 2,
  "format": "png",
  "maxInputMegapixels": 2
}
```

# Actor output Schema

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

One record per input URL with ok, input and output sizes, megapixels charged, the file key and its URL, or an error code.

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

The enlarged PNG, JPEG or WebP files in the run's key-value store.

# 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 = {
    "urls": [
        "https://raw.githubusercontent.com/python-pillow/Pillow/main/Tests/images/flower.jpg"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("hermeskd/image-upscale").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 = { "urls": ["https://raw.githubusercontent.com/python-pillow/Pillow/main/Tests/images/flower.jpg"] }

# Run the Actor and wait for it to finish
run = client.actor("hermeskd/image-upscale").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 '{
  "urls": [
    "https://raw.githubusercontent.com/python-pillow/Pillow/main/Tests/images/flower.jpg"
  ]
}' |
apify call hermeskd/image-upscale --silent --output-dataset

```

## MCP server setup

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

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/cA0Y6a3YwNeGY9cob/builds/UHEf5p7H7jGwoGvTi/openapi.json
