ICO to ICO API
POST an icon to /v2/convert/ico/to/ico and get a new .ico holding a single image. The API takes the best entry of your file, applies any resize you request, and stores the result as a 32-bit PNG inside a fresh icon directory.
- Endpoint
- POST /v2/
convert/ ico/ to/ ico - Accepts
- ico
- Sample timing
- 2 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/ico \
-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/ico",
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/ico";
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.002,
"Files": [
{
"FileName": "paper-plane-app-icon.ico",
"FileExt": "ico",
"FileSize": 24368,
"FileData": "AAABAAEAAAAAAAAAIAAaXwAA…"
}
]
}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.ico (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 new icon. ImageWidth=128 gave an 11,021-byte 128 × 128 .ico. Requests above 256 are accepted but fitted back into 256 × 256: ImageWidth=640 returned a 256 px icon, resampled twice.Values: 1–20000 |
ImageHeightinteger | Height of the new icon; ImageHeight=100 produced 100 × 100 at 8,198 bytes.Values: 1–20000 |
ScaleProportionsboolean, default true | false stretches the image into a non-square box. With a square icon and a square box it changes nothing. |
ScaleIfLargerboolean, default false | Prevents enlarging, which here only matters for icons smaller than the size you ask for. |
ScaleImageboolean, default true | false trims the icon to the given size from the top-left without resampling it. |
ColorSpaceenum, default default | Gray or two-tone icons, still with alpha: 22,410 bytes for gray, 9,897 for monochrome on the sample.Values: default, rgb, gray, monochrome |
ImageResolutioninteger | Validated, then dropped, since an icon directory has no DPI field.Values: 10–800 |
ImageInterpolationboolean, default false | Either value works; downscaling uses Lanczos. |
FileNamestring | Name of the new .ico; defaults to the uploaded file's name. |
StoreFileboolean, default false | Hold the icon for 3 hours and return its URL. |
What carries over, and what doesn't
Kept
- The chosen entry's pixels and alpha: the sample's new icon decodes to exactly the old 256 px image
- 32-bit RGBA, so semi-transparent edges keep their values
- A valid icon directory: type 1, one entry marked 32 bits per pixel, PNG data
Lost or not yet supported
- All sizes but one. Four entries went in and one came out; there is no option to build a multi-size icon yet.
- Bitmap-format entries. Every output entry is PNG-compressed.
- Hand-drawn small sizes: asking for 48 px scales the 256 px art down instead of keeping the 48 px design stored in the file.
- Cursor type and hotspot. A .cur input comes back as an ordinary icon.
Limits
- One icon per request.
- Requests up to 10 MB on the Free plan or 100 MB on paid plans, all files and fields together.
- The output entry is at most 256 × 256; larger sizes are fitted back down.
- 120 seconds per conversion, then 504.
Where teams use it
Trim icons before you ship them
An icon with many sizes can be cut down to the one size you serve. The sample went from 34,551 bytes with four entries to 24,368 bytes with only the 256 px image.
A smaller icon from a large one
ImageWidth=48 turns a 256 px icon into a 48 px .ico in one call, for places that load a single small icon file.
Normalizing icons from many sources
Icons uploaded by users come with bitmap entries, PNG entries, palettes and odd sizes. Repacking gives you one 32-bit PNG-compressed entry, or a 422 when the file is damaged or holds a PNG entry without alpha.
Questions
Can the API build a multi-size .ico with 16, 32, 48 and 256 px?
No. The output always holds one image. To produce several sizes you would need one request per size and a tool on your side to combine the entries.
Why is the new icon smaller than mine?
The three smaller entries are gone. The remaining 256 px image is compressed again and comes out about 25% larger than before (24,346 bytes against 19,449 in the original), but the file as a whole shrank from 34,551 to 24,368 bytes.
What happens if I ask for 640 px?
An icon entry can't be bigger than 256 × 256, so the image is first scaled to your size and then fitted back into 256 px. Ask for 256 or less to avoid resampling twice.
Does it repair damaged icons?
No. A truncated .ico returns 422 with "the input file could not be read: the image is cut short: its data ends before its last pixel". Only icons our decoder can read get repacked.
Convert your first ICO file in a minute.