ICO to PNG API
POST an icon to /v2/convert/ico/to/png and get one PNG back as base64 in the JSON. The converter takes a single image out of the .ico (the entry with the most color depth, then the biggest) and writes its pixels and alpha channel unchanged.
- Endpoint
- POST /v2/
convert/ ico/ to/ png - Accepts
- ico
- Sample timing
- 1 ms median of 5 runsHow we measure
- Request size
- Up to 10 MB on the Free plan or 100 MB on paid plans, all files together
Request and response
Every sample below ran against the API before this page was published. Set PDFCONVERSIONAPI_KEY to your key and run it as is.
curl -sS -X POST \
https://api.pdfconversionapi.com/v2/convert/ico/to/png \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F File=@paper-plane-app-icon.ico \
-o response.json
# FileData is base64. Save the first file in converted/,
# or print the JSON error.
if jq -e '.Files' response.json > /dev/null; then
mkdir -p converted
name=$(jq -r '.Files[0].FileName' response.json)
jq -r '.Files[0].FileData' response.json \
| base64 --decode > "converted/$name"
else
cat response.json
fiimport base64
import os
import requests
key = os.environ["PDFCONVERSIONAPI_KEY"]
response = requests.post(
"https://api.pdfconversionapi.com/v2/convert/ico/to/png",
headers={"Authorization": f"Bearer {key}"},
files=[("File", open("paper-plane-app-icon.ico", "rb"))],
timeout=180,
)
result = response.json()
if not response.ok:
raise SystemExit(f"{result['Code']}: {result['Message']}")
os.makedirs("converted", exist_ok=True)
for file in result["Files"]:
path = os.path.join("converted", file["FileName"])
with open(path, "wb") as out:
out.write(base64.b64decode(file["FileData"]))
print(path, file["FileSize"], "bytes")
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
const form = new FormData();
const file = "paper-plane-app-icon.ico";
form.append("File", new Blob([await readFile(file)]), file);
const url = "https://api.pdfconversionapi.com/v2/convert/ico/to/png";
const key = process.env.PDFCONVERSIONAPI_KEY;
const response = await fetch(url, {
method: "POST",
headers: { Authorization: `Bearer ${key}` },
body: form,
});
const result = await response.json();
if (!response.ok) {
throw new Error(`${result.Code}: ${result.Message}`);
}
await mkdir("converted", { recursive: true });
for (const file of result.Files) {
const path = join("converted", file.FileName);
await writeFile(path, Buffer.from(file.FileData, "base64"));
console.log(path, file.FileSize, "bytes");
}
{
"ConversionCost": 1,
"ConversionTime": 0.001,
"Files": [
{
"FileName": "paper-plane-app-icon.png",
"FileExt": "png",
"FileSize": 24346,
"FileData": "iVBORw0KGgoAAAANSUhEUgAA…"
}
]
}Response from our test run on 2026-11-03. ConversionTime shows the median of 5 runs. FileData is shortened here; the real response carries the whole file as base64.
The sample, converted
The same file the code sends. Download the input and the output and compare them yourself.

- Input
- paper-plane-app-icon.ico (34 KB)
- Output
- paper-plane-app-icon.png (24 KB)
- Source
- A four-size app icon we drew and assembled ourselves: 16, 32 and 48 px entries stored as 32-bit bitmaps with a 1-bit mask, and a 256 px entry stored as PNG. The paper plane design is fictional. (CC0-1.0). How it was made
- Made by
- 2026-11-04, API build
9eeeb0640aba
Parameters
Send these as form fields next to the file. Names are case-insensitive. If you set one of the documented options below that we don't support yet, the request fails with a 400 that names it instead of returning a file made without it.
| Parameter | What it does |
|---|---|
ImageWidthinteger | Width of the PNG in pixels. On the sample icon, ImageWidth=128 returns a 128 × 128 PNG of 10,999 bytes; ImageWidth=640 enlarges the 256 px entry to 640 × 640.Values: 1–20000 |
ImageHeightinteger | Height in pixels. Sent alone it derives the width (ImageHeight=100 gives 100 × 100); with ImageWidth the pair is a box the icon is fitted into.Values: 1–20000 |
ScaleProportionsboolean, default true | false stretches the image to exactly ImageWidth × ImageHeight. For a square icon in a square box both settings return the same bytes. |
ScaleIfLargerboolean, default false | true turns resizing into shrink-only. ImageWidth=640 plus ScaleIfLarger=true hands back the 256 × 256 original instead of a blown-up copy. |
ScaleImageboolean, default true | false crops instead of scaling. ImageWidth=100, ImageHeight=80 and ScaleImage=false cut a 100 × 80 piece from the top-left corner of the icon. |
ColorSpaceenum, default default | gray writes a gray PNG that still has its alpha channel (11,897 bytes for the sample). monochrome thresholds each pixel to black or white, alpha kept. cmyk returns 400.Values: default, rgb, gray, monochrome |
TransparentColorstring | Pixels of exactly this color become fully transparent. Careful with white: on the sample, #FFFFFF also removed the 3,398 white pixels of the paper plane.Values: Color name, #RRGGBB or R,G,B |
ImageResolutioninteger | DPI stored in a pHYs chunk. Without it the PNG has no resolution chunk, whatever the icon contained.Values: 10–800 |
ImageInterpolationboolean, default false | Accepted with either value. Any resize uses a Lanczos filter regardless. |
FileNamestring | Name of the result without extension. By default paper-plane-app-icon.ico comes back as paper-plane-app-icon.png. |
StoreFileboolean, default false | true stores the PNG for 3 hours and returns a link in Files[].Url instead of base64 FileData. |
What carries over, and what doesn't
Kept
- The chosen entry's color and alpha values exactly: the sample's 256 px image came back identical, including its 12,009 partly transparent shadow and edge pixels
- Entries stored either way inside the .ico: as PNG, or as a bitmap with a 1-bit mask, which becomes real transparency
- The entry's own size; nothing is padded, cropped or scaled unless you ask
- 8 bits per channel RGBA, written as a standard 32-bit PNG
Lost or not yet supported
- Every other size in the file. The sample stores 16, 32, 48 and 256 px images; only the 256 px one is converted.
- The stored PNG bytes. The entry is decoded and compressed again, so the 19,449-byte PNG inside the sample icon came out as a 24,346-byte file with the same pixels.
- Hand-tuned small sizes.
ImageWidth=48scales the large image down; it does not pick the 48 px artwork stored in the icon. - Cursor hotspots. A .cur file converts on this route too, but the hotspot position is not kept.
Limits
- One .ico per request; a second file returns 400.
- Requests up to 10 MB on the Free plan or 100 MB on paid plans, all files and fields together.
- Enlarged results are capped at 100,000,000 pixels:
ImageWidth=12000on a square icon returns 413. - 120 seconds per conversion, then 504.
Where teams use it
Favicons collected by your app
Link previews, bookmark tools and site directories often pick up a site's /favicon.ico. Converting it to PNG when you fetch it means the rest of your code handles one common image format, transparency included.
Artwork from Windows program icons
Icon files shipped with desktop software hold several sizes of the same picture. The PNG gives you the best one as an ordinary image for a download page, a catalog or documentation.
Auditing an icon set
The PNG's dimensions tell you the largest entry among those with the most bits per pixel. When every entry is 32-bit, an icon that converts to 32 × 32 has no large version, which is worth flagging before it shows up blurry on a high-density screen.
Questions
Which image does the API take from a multi-size .ico?
One entry: the one with the most bits per pixel, and among those the largest. All four entries in the sample are 32-bit, so the 256 px one is used. In a test icon holding a 32-bit 48 px entry and an 8-bit 256 px entry, the 48 px entry won.
Why does my icon return 422 "The PNG is not in RGBA format"?
An entry stored as PNG has to be 32-bit RGBA for our decoder. A test icon whose 256 px entry was an RGB PNG without alpha failed with exactly that message. Re-save the icon with an alpha channel, or convert the original PNG artwork instead.
Can I get every size in the icon as a separate PNG?
Not today. Each request returns one PNG. Other sizes can be made with ImageWidth, but they are scaled from the chosen entry, not taken from the matching entry in the file.
Does the route accept .cur cursor files?
Yes. A 48 px test cursor converted to a 48 × 48 PNG. The cursor's hotspot coordinates are dropped, because a PNG has no place for them.
Convert your first ICO file in a minute.