ICO to WebP API
POST an icon to /v2/convert/ico/to/webp and get a lossless WebP back. The API decodes the best entry in the .ico and encodes it with its alpha channel, or as plain RGB when no pixel is transparent; this route has no lossy mode, so what you get decodes to the icon's own pixels.
- Endpoint
- POST /v2/
convert/ ico/ to/ webp - 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/webp \
-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/webp",
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/webp";
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.webp",
"FileExt": "webp",
"FileSize": 21264,
"FileData": "UklGRghTAABXRUJQVlA4TPtS…"
}
]
}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.webp (21 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 | Target width. The sample's 256 px image becomes a 9,808-byte WebP at ImageWidth=128.Values: 1–16384 |
ImageHeightinteger | Target height; alone it keeps the square shape (ImageHeight=100, 7,372 bytes). Combined with a width it sets a bounding box.Values: 1–16384 |
ScaleProportionsboolean, default true | Set to false to fill a non-square ImageWidth × ImageHeight box exactly, distorting the icon. |
ScaleIfLargerboolean, default false | With true, sizes bigger than the icon are ignored, so a 640 px request leaves the 256 px image as it is. |
ScaleImageboolean, default true | false keeps pixels at 1:1 and trims the image to the requested size from the top-left. |
ColorSpaceenum, default default | gray gave a 10,916-byte WebP and monochrome a 4,516-byte one, both with the alpha channel still in place.Values: default, rgb, gray, monochrome |
TransparentColorstring | Clears every pixel of one exact color. #FFFFFF on the sample clears only the 3,398 pure white pixels of the paper plane; the blue-green tile has no pure white and stays opaque (21,484 bytes).Values: Color name, #RRGGBB or R,G,B |
ImageResolutioninteger | Accepted, including ConvertAPI's documented 200, but WebP has no DPI field, so nothing is written.Values: 10–800 |
ImageInterpolationboolean, default false | Either value is accepted; scaling is always done with a Lanczos filter. |
FileNamestring | Base name for the .webp. Defaults to the uploaded icon's name. |
StoreFileboolean, default false | Return a download URL valid for 3 hours instead of the file in the JSON. |
Not supported yet
Setting one of these to anything but its default returns 400.
| Option | What to do instead |
|---|---|
ImageQuality | The WebP is always lossless. ConvertAPI's documented default of 75 is accepted; any other value returns 400 "ImageQuality is not supported yet for ICO → WebP". |
What carries over, and what doesn't
Kept
- Every pixel and alpha value of the chosen entry: the sample WebP decodes to the same RGBA data as the 256 px image in the icon
- Partial transparency: all 12,009 partly transparent shadow and edge pixels keep their exact alpha
- The 256 × 256 size of the largest entry, unless you resize
Lost or not yet supported
- The 16, 32 and 48 px entries of the sample. The WebP holds one image.
- A lossy option. Photo-like icons stay larger than a lossy WebP would make them, because only the lossless bitstream is written.
- DPI.
ImageResolutionis accepted, but there is nowhere in a WebP to put it. - Hotspots from .cur cursors, which also convert on this route.
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 scaled WebP may be at most 16,384 px on a side and 100,000,000 pixels in all. The icon stretched to 16,385 × 100 returned 413 "WebP images can be at most 16,384 pixels on a side, and this one is 16,385×100; lower ImageWidth or ImageHeight"; at 16,384 × 100 it converted (462,002 bytes). Resize values up to 20,000 are accepted, so the 413 comes after resizing.
- 120 seconds per conversion, then 504.
Where teams use it
Icons served on web pages
Sites that already deliver images as WebP can take app or category icons uploaded as .ico and store them the same way. For the sample, the lossless WebP was 21,264 bytes against 24,346 for the PNG of the same pixels.
One format in a mobile or CDN pipeline
When an image service or app bundle expects WebP, converting .ico uploads on arrival avoids a separate code path for icons. Soft shadows and clear corners survive because the alpha channel is stored losslessly.
Questions
Is the WebP lossless?
Yes. Our encoder writes the lossless WebP format (VP8L). Decoding the sample's WebP gave back exactly the RGBA values of the icon's 256 px entry.
Why does ImageQuality=80 return 400?
ImageQuality is one of ConvertAPI's documented options for this conversion, and we refuse documented options we can't honor instead of ignoring them. The only accepted value is ConvertAPI's default, 75, which changes nothing.
How does the size compare with PNG and the original .ico?
For the sample, the four-size .ico was 34,551 bytes, the PNG of its 256 px entry 24,346 bytes and the lossless WebP 21,264 bytes, about 13% smaller than the PNG.
Which entry of the icon is converted?
The one with the most bits per pixel, then the largest. When every entry is 32-bit, as in the sample, that is the biggest one, here 256 px; smaller entries are not used.
Convert your first ICO file in a minute.