Up to 3 upscales a day per network address. Output up to 16 400 px. How images are handled
Examples
Use the MegaPotato API
The same upscaler from your own code: a REST API with three calls. Create an API key (free test keys right away; live keys as described in the docs), keep it on your server and send it as a Bearer token. Full documentation: megapotato.app/developers · OpenAPI.
import os, time, uuid, requests
API = "https://megapotato.app"
AUTH = {"Authorization": f"Bearer {os.environ['MEGAPOTATO_API_KEY']}"}
def upscale(path, key, factor=4, out=None, timeout=600):
"""Upscale one photo. Retrying with the same `key` is never charged twice."""
with open(path, "rb") as f:
r = requests.post(f"{API}/v1/upscale", files={"file": f}, data={"upscale": factor},
headers={**AUTH, "Idempotency-Key": key}, timeout=120)
r.raise_for_status()
job, deadline = r.json(), time.monotonic() + timeout
while job["status"] != "completed" or not job.get("result_url"):
if job["status"] == "failed":
raise RuntimeError(job["error"]) # the chips are refunded
if time.monotonic() > deadline:
raise TimeoutError(f"job {job['id']} is still running; poll it again later")
try:
r = requests.get(f"{API}/v1/jobs/{job['id']}", params={"wait": 55}, headers=AUTH, timeout=90)
except requests.RequestException:
time.sleep(5) # transient network error: polling is always safe to repeat
continue
if r.status_code == 429 or r.status_code >= 500:
time.sleep(int(r.headers.get("Retry-After", 5)))
continue
r.raise_for_status()
job = r.json()
png = requests.get(job["result_url"], timeout=300) # the link is valid for about an hour
png.raise_for_status()
with open(out or f"{os.path.splitext(path)[0]}_x{factor}.png", "wb") as f:
f.write(png.content)
# Free price check first: chips for a 1024 x 768 photo at x4
print(requests.post(f"{API}/v1/quote", headers=AUTH,
json={"width": 1024, "height": 768, "upscale": 4}).json())
key = str(uuid.uuid4()) # keep it, e.g. next to the file name, to retry safely
upscale("photo.jpg", key, factor=4)
set -o pipefail
API=https://megapotato.app
AUTH="Authorization: Bearer $MEGAPOTATO_API_KEY"
KEY=$(uuidgen) # keep it: retrying with the same key is never charged twice
JOB=$(curl -sf --max-time 120 "$API/v1/upscale" -H "$AUTH" -H "Idempotency-Key: $KEY" \
-F file=@photo.jpg -F upscale=4 | jq -er .id) || exit 1
for i in $(seq 1 12); do # up to about 11 minutes
RES=$(curl -sf --max-time 90 "$API/v1/jobs/$JOB?wait=55" -H "$AUTH") || { sleep 5; continue; }
case $(jq -r .status <<<"$RES") in
completed) URL=$(jq -r '.result_url // empty' <<<"$RES"); [ -n "$URL" ] && break ;;
failed) jq -r .error <<<"$RES"; exit 1 ;;
esac
done
[ -n "$URL" ] || { echo "job $JOB is still running; poll it again later"; exit 1; }
curl -sf --max-time 300 "$URL" -o photo_x4.png
// Server-side only: never put an API key in browser code.
import { randomUUID } from "node:crypto";
import { readFile, writeFile } from "node:fs/promises";
const API = "https://megapotato.app";
const auth = { Authorization: `Bearer ${process.env.MEGAPOTATO_API_KEY}` };
export async function upscale(path, key, factor = 4, timeoutMs = 600_000) {
// Retrying with the same `key` is never charged twice.
const form = new FormData();
form.append("file", new Blob([await readFile(path)]), "photo");
form.append("upscale", String(factor));
const res = await fetch(`${API}/v1/upscale`, {
method: "POST", body: form, headers: { ...auth, "Idempotency-Key": key }, signal: AbortSignal.timeout(120_000),
});
if (!res.ok) throw new Error(`submit failed: ${res.status} ${await res.text()}`);
let job = await res.json();
const deadline = Date.now() + timeoutMs;
while (job.status !== "completed" || !job.result_url) {
if (job.status === "failed") throw new Error(job.error); // the chips are refunded
if (Date.now() > deadline) throw new Error(`job ${job.id} is still running; poll it again later`);
const r = await fetch(`${API}/v1/jobs/${job.id}?wait=55`, { headers: auth, signal: AbortSignal.timeout(90_000) })
.catch(() => null);
if (!r || r.status === 429 || r.status >= 500) {
await new Promise((ok) => setTimeout(ok, 5000));
continue;
}
if (!r.ok) throw new Error(`poll failed: ${r.status}`);
job = await r.json();
}
const png = await fetch(job.result_url, { signal: AbortSignal.timeout(300_000) }); // the link is valid for about an hour
if (!png.ok) throw new Error(`download failed: ${png.status}`);
await writeFile(path.replace(/\.\w+$/, "") + `_x${factor}.png`, Buffer.from(await png.arrayBuffer()));
}
const key = randomUUID(); // keep it to retry safely
await upscale("photo.jpg", key, 4);
How it works
- Price
POST /v1/quotereturns the chips for any size, free. - Submit
POST /v1/upscaleholds the chips and queues the job. - Poll
GET /v1/jobs/{id}?wait=55waits for the result; failed jobs are refunded.
Limits
| Scale | ×1 (refine at the same size), ×2, ×4, ×8 |
|---|---|
| Input | up to 6 000 px per side, 20.25 MP, 40 MB; PNG, JPEG, WebP, HEIC |
| Output | up to 16 400 px per side, 269 MP, 8-bit PNG |
| Results | downloadable for 90 days |
| Price | per photo, quoted in chips by /v1/quote · Pricing |
Also: the ComfyUI node (search “MegaPotato” in ComfyUI Manager) and more examples on GitHub.
What this demo is
A free demo of the MegaPotato AI upscaler, an image super-resolution model for photos. This page is a thin client: your image goes to the MegaPotato API, is upscaled on MegaPotato's GPUs and comes back here. The model weights are not published.
In our October 2026 benchmark of 18 AI upscalers (blind AI judges and image metrics, open data under CC BY 4.0) the model came first and was the cheapest of the leaders. MegaPotato is one of the products compared; the method is published.
Your images and privacy
Images you upscale are sent through a small MegaPotato service on Cloudflare to the MegaPotato API and handled under its Privacy Policy and Terms. Every image is reviewed automatically before processing, and images that may not meet our content rules are refused. The original is deleted once the upscale finishes and the result about an hour after the upload. The demo asks the Hugging Face page only whether you are logged in (not who you are) and counts runs per network address, stored only as a keyed hash; Cloudflare processes it to run the service and the Turnstile check that helps limit automated abuse. Results open only inside this demo page. Please upload only images you have the right to use.
Example image credits
All public domain, reduced in size for the demo and upscaled once:
Demo code: MIT license. Fonts: Bricolage Grotesque, Hanken Grotesk, JetBrains Mono (SIL Open Font License).