# Bulk AI Image Upscaler from CSV, Google Sheet or Dataset (`nerolabs/bulk-image-upscaler`) Actor

Upscales every image linked in a dataset, CSV, Excel file or Google Sheet 2x, 3x or 4x with AI (Real-ESRGAN) and stores each with a public link, keeping your columns. Inputs: datasetId or fileUrl, urlField, scale. Charged per image delivered, plus per extra input megapixel. Agent-ready: x402, MCP.

- **URL**: https://apify.com/nerolabs/bulk-image-upscaler.md
- **Developed by:** [Adam Pearce](https://apify.com/nerolabs) (community)
- **Categories:** AI, E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.50 / 1,000 image 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

## Bulk AI Image Upscaler from CSV, Google Sheet or Dataset

**Got a whole catalogue of small, blurry product photos?** Point this actor at your spreadsheet, Google Sheet or scraper results and every image comes back 2x, 3x or 4x bigger and sharper, each with its own download link, sitting next to your original columns (SKU, name, price, whatever you had).

No uploading one image at a time. No GPU, no subscription, no API key.

### What it does

1. Reads your rows from a **CSV, Excel file, Google Sheet, Apify dataset, JSON file** or a plain list of image links.
2. Finds the image link in each row (or uses the column you name).
3. Upscales each image with **Real-ESRGAN**, a well-known AI upscaling model that sharpens edges and cleans up compression blur.
4. Stores every result with a **public link** and writes one output row per input row, keeping your own columns.

Typical uses:

- **Ecommerce**: small supplier photos to marketplace-ready sizes (Amazon, Etsy, Shopify zoom).
- **Property and car listings**: old, low-resolution listing photos made presentable.
- **After a scraper**: upscale every image a scraper found, straight from its dataset.
- **With [Bulk Image & File Downloader](https://apify.com/nerolabs/bulk-file-downloader)**: download a list of images, then upscale them (use `fileUrl` as the link column), or chain both in one run with [Actor Pipeline Runner](https://apify.com/nerolabs/actor-pipeline-runner).

### Example

Input: a CSV with `sku, name, price, imageUrl`, each image 320 x 240.

| sku | name | upscaledUrl | inputWidth | inputHeight | outputWidth | outputHeight | status |
|---|---|---|---|---|---|---|---|
| NL-2001 | Stamp Mug | https://api.apify.com/v2/key-value-stores/.../upscaled-sample-lowres-mug.jpg | 320 | 240 | 1280 | 960 | upscaled |
| NL-2002 | Glass Teapot | https://api.apify.com/v2/key-value-stores/.../upscaled-sample-lowres-teapot.jpg | 320 | 240 | 1280 | 960 | upscaled |

Every output row also has `sourceUrl`, `upscaledKey`, `scale`, `format`, `sizeBytes`, `processedInputWidth/Height`, `resizedToFitCap`, `processingSeconds`, `chargedLargeImageMegapixels` and a plain-English `statusDetail`.

### Pricing

Pay per event, only for what you receive:

| Event | Price | When |
|---|---|---|
| Image upscaled | **$0.015** per image (Bronze $0.0135, Silver $0.012, Gold $0.0105) | Each image upscaled and stored |
| Large image megapixel | $0.015 per megapixel | Each started megapixel of input **above the first 1 MP** (about 1000 x 1000). A 2000 x 2000 input adds 3. |

Worked examples:

- **100 product photos around 600 x 600**: 100 x $0.015 = **$1.50**.
- **1,000 photos around 800 x 600**: **$15** (Gold plan: $10.50).
- **One 2000 x 2000 photo**: $0.015 + 3 x $0.015 = $0.06 (the output is 8000 x 8000).

Never charged: rows with no link, failed or blocked downloads, links that are web pages rather than images, repeated links (they point at the first result), images skipped by *Skip images at least this big*, and anything beyond your run's spending limit. If your spending limit is reached, the remaining rows are marked `skipped_budget` and any image that could not be paid for is deleted, not kept.

### Input

- **File or Google Sheet URL** (`fileUrl`), **Dataset** (`datasetId`), **Image URLs** (`imageUrls`) or **Rows** (`data`): use one.
- **Link column** (`urlField`): leave empty to detect it.
- **Upscale factor** (`scale`): 4 (default), 3 or 2.
- **Output format**: Auto (JPEG, or PNG when the image has transparency), JPEG, PNG or WebP. Transparency is preserved.
- **Largest input** (`maxInputMegapixels`, default 4): bigger inputs are shrunk first so a huge photo cannot produce a gigantic file or a large charge.
- **Skip images at least this big** (`skipIfLongEdgeAtLeast`): leave already-large photos alone, uncharged.
- **Name files by column** (`fileNameField`): name each file after your SKU or ID.
- **Keep images in a named store** (`storeName`): keep the files beyond the run's normal data retention.
- **Max images**, **Max rows**, download size limit and timeout.

Google Sheets: share as "Anyone with the link can view" and paste the normal link.

### Speed

It runs on normal Apify CPUs (no GPU). At the default 4 GB of memory a 600 x 600 photo takes about 13 seconds and a 320 x 240 thumbnail about 3 seconds, so roughly 200 to 250 typical product photos an hour. For big batches, raise the run's memory: every 4 GB adds a CPU core and one more image processed at the same time, so 16 GB handles about four times as many photos an hour. The price per image is the same at any memory size. Long runs save each row as it finishes, so nothing is lost if a run hits its timeout (raise the timeout for very long lists).

### Honest limits

- The model sharpens and cleans; it does not invent detail that was never there. Very fine textures (fabric weave, hair) can look slightly smooth. Faces are not specially restored.
- Links must point straight at an image file. A product page link returns `not_an_image` (not charged); use a scraper or [Bulk Image & File Downloader](https://apify.com/nerolabs/bulk-file-downloader) to get the direct image links first.
- Animated GIFs: only the first frame is upscaled.
- The actor never fetches private or internal network addresses.
- Images are stored in the run's key-value store and follow your Apify plan's data retention, unless you set a named store.

### For AI agents and integrations

Send rows in `data` or a file in `fileUrl`, read `upscaledUrl` from each output row. Pay per event via x402 and MCP; no API key needed. Works with the Apify API, n8n, Make and Zapier.

### FAQ

**Is this free for commercial use?** Yes. The model, Real-ESRGAN general x4v3 by Xintao Wang et al., is released under the BSD-3-Clause licence, and you own your images.

**Why not a heavier "generative" upscaler?** Those invent new detail with a diffusion model and cost several times more per image. This one is predictable, keeps the product looking like the product, and handles whole lists.

**Can I upscale only the small images in a mixed list?** Yes: set *Skip images at least this big* to, say, 1500 px.

**Did it help?** If this saved you an afternoon of one-at-a-time uploads, a quick review on the Store helps a lot. Questions or a feature you need: open an issue on the Issues tab and I reply personally.

Model credit: Real-ESRGAN, Xintao Wang, Liangbin Xie, Chao Dong, Ying Shan (BSD-3-Clause).

# Actor input Schema

## `fileUrl` (type: `string`):

Link to a CSV, TSV, Excel (.xlsx), JSON or JSON Lines file, or a Google Sheets link (share it as 'Anyone with the link can view'; a normal edit link is converted automatically). One row per image. Use this, 'datasetId', 'imageUrls' or 'data', only one.

## `datasetId` (type: `string`):

An Apify dataset whose rows hold image links, for example the output of a scraper or of Bulk Image & File Downloader (use 'fileUrl' as the link column).

## `imageUrls` (type: `array`):

A plain list of direct image links, if you have no file or dataset. Each becomes one output row.

## `data` (type: `array`):

Rows as JSON objects, for callers that send data directly (agents, API, n8n, Make). Each row needs a column holding an image link.

## `urlField` (type: `string`):

The column holding each row's image link. Leave empty to detect it (a column named like imageUrl, image, photo or url, else the column with the most links). A cell holding several links or a list uses the first one.

## `scale` (type: `string`):

How much bigger each side gets. 4x turns 500 x 500 into 2000 x 2000. The model always works at 4x; 2x and 3x are resized down from that result, so they cost the same.

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

File type of the upscaled images. Auto keeps transparency by using PNG only for images that have it.

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

Compression quality for JPEG and WebP output, 40 to 100.

## `maxInputMegapixels` (type: `number`):

Inputs bigger than this are shrunk (keeping their shape) before upscaling, so a huge photo cannot produce a gigantic output or a large charge. 4 MP is about 2000 x 2000, which gives an 8000 x 8000 output at 4x. Rows where this happened show resizedToFitCap: true.

## `skipIfLongEdgeAtLeast` (type: `integer`):

Skip, and never charge for, images whose longest side is already at least this many pixels. Useful for mixed catalogues where only the small photos need upscaling. 0 upscales everything.

## `fileNameField` (type: `string`):

Optional column (for example sku or id) used to name each stored file, as upscaled-<value>.jpg. Default is the source file's own name.

## `keepOriginalFields` (type: `boolean`):

Copy each input row's columns into its output row, next to the upscaled image link.

## `storeName` (type: `string`):

Optional named key-value store to save the images in, so they outlive the run's normal data retention. Leave empty to use the run's own storage.

## `maxImages` (type: `integer`):

Stop after this many images have been upscaled (and charged). 0 means no limit.

## `maxItems` (type: `integer`):

Read at most this many input rows.

## `fileFormat` (type: `string`):

Format of 'fileUrl'. Auto detects it from the link, the server's content type or the content.

## `sheetName` (type: `string`):

For Excel files, the sheet to read. Default is the first sheet.

## `maxDownloadMb` (type: `integer`):

Image downloads larger than this are skipped (not charged).

## `requestTimeoutSecs` (type: `integer`):

How long to wait for one image download before giving up on it (not charged).

## Actor input object example

```json
{
  "fileUrl": "https://nerolabs-samples.nerolabs.workers.dev/sample-lowres-product-photos.csv",
  "urlField": "imageUrl",
  "scale": "4",
  "outputFormat": "auto",
  "quality": 92,
  "maxInputMegapixels": 4,
  "skipIfLongEdgeAtLeast": 0,
  "keepOriginalFields": true,
  "maxImages": 0,
  "fileFormat": "auto",
  "maxDownloadMb": 50,
  "requestTimeoutSecs": 60
}
```

# Actor output Schema

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

One row per input row with the upscaled image's public link, sizes and status, next to your original columns.

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

Every upscaled image and the summary, in the run's key-value store.

## `summary` (type: `string`):

Rows read, images upscaled, status counts and warnings.

# 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 = {
    "fileUrl": "https://nerolabs-samples.nerolabs.workers.dev/sample-lowres-product-photos.csv",
    "urlField": "imageUrl"
};

// Run the Actor and wait for it to finish
const run = await client.actor("nerolabs/bulk-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 = {
    "fileUrl": "https://nerolabs-samples.nerolabs.workers.dev/sample-lowres-product-photos.csv",
    "urlField": "imageUrl",
}

# Run the Actor and wait for it to finish
run = client.actor("nerolabs/bulk-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 '{
  "fileUrl": "https://nerolabs-samples.nerolabs.workers.dev/sample-lowres-product-photos.csv",
  "urlField": "imageUrl"
}' |
apify call nerolabs/bulk-image-upscaler --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nerolabs/bulk-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/BQJApdxQF4yHscBcZ/builds/9kEbhRqFHrECVqfCo/openapi.json
