QOI to ICO API
POST a QOI to /v2/convert/qoi/to/ico and get a Windows icon with one 32-bit entry. The 400 × 300 sample was fitted to 256 × 192, keeping its 4:3 shape and soft alpha.
- Endpoint
- POST /v2/
convert/ qoi/ to/ ico - Accepts
- qoi
- Sample timing
- 4 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/qoi/to/ico \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F File=@trail-map-card.qoi \
-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/qoi/to/ico",
headers={"Authorization": f"Bearer {key}"},
files=[("File", open("trail-map-card.qoi", "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 = "trail-map-card.qoi";
form.append("File", new Blob([await readFile(file)]), file);
const url = "https://api.pdfconversionapi.com/v2/convert/qoi/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.004,
"Files": [
{
"FileName": "trail-map-card.ico",
"FileExt": "ico",
"FileSize": 29495,
"FileData": "AAABAAEAAMAAAAAAIAAhcwAA…"
}
]
}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
- trail-map-card.qoi (66 KB)
- Output
- trail-map-card.ico (29 KB)
- Source
- A 400 x 300 trail map card with a sunrise sky, rounded transparent corners and a soft drop shadow, drawn by us and saved as a 4-channel QOI. (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 | Choose a smaller icon: 128 gave a 128 × 96 entry of 12,067 bytes. Values above 256 are fitted back down, and the second resampling changes the pixels slightly.Values: 1–20000 (icon capped at 256) |
ImageHeightinteger | Icon height; with a width, the image fits in the box.Values: 1–20000 (capped at 256) |
ScaleProportionsboolean, default true | false with ImageWidth and ImageHeight both set gives an exactly square icon (200 × 200 from the 4:3 card). |
ScaleIfLargerboolean, default false | true won't enlarge a small QOI to the size you ask for. |
ScaleImageboolean, default true | false crops the requested size from the top-left. |
ColorSpaceenum, default default | Turns the icon gray (26,665 bytes) or black and white (4,785 bytes, strictly the levels 0 and 255 after the fit to 256 px) while keeping the alpha.Values: default, rgb, gray, monochrome; cmyk returns 400 |
FileNamestring | Name for the .ico. |
StoreFileboolean, default false | Return a 3-hour URL instead of the data. |
What carries over, and what doesn't
Kept
- The 4:3 shape: 400 × 300 became 256 × 192, the largest size that fits
- Soft transparency, since the entry is a PNG with 8-bit alpha
- Colors, apart from the blending a downscale brings
Lost or not yet supported
- Resolution above 256 px per side, the ICO maximum. The sample lost about 36% of its width.
- A square shape, unless you ask for one: the sample's entry is 256 × 192, and padding to a square is not offered.
- Multiple sizes: the file has exactly one entry.
Limits
- One QOI per request, one icon entry out, at most 256 × 256.
- Very thin images shrink to almost nothing: a 70,000 × 2 QOI became a 256 × 1 icon.
- 100,000,000 pixels per image; requests up to 10 MB on the Free plan or 100 MB on paid plans.
- 120 seconds per conversion.
Where teams use it
App icons from QOI artwork
If your build or art tools export QOI, the icon for an installer or shortcut can come straight from the same file, with no separate PNG step.
Favicons with soft edges
The icon entry keeps 8-bit alpha, so anti-aliased rounded corners look smooth on light and dark tabs. Request 32 or 48 px with ImageWidth.
Questions
How do I get a square icon from a non-square QOI?
Send ImageWidth=256, ImageHeight=256 and ScaleProportions=false to stretch it, or ScaleImage=false to crop a square from the top-left. Padding to a square is not available, so pad the source if the shape must not change.
What does the ICO directory say about the size?
Width 0, which means 256 in the ICO format, and height 192. The image data is a 29,473-byte PNG, so the whole file is 29,495 bytes.
Can I put 16, 32 and 48 px versions in one file?
Not in one request. Each call writes a single entry, so request each size separately and merge them with an icon tool if you need a multi-size file.
Convert your first QOI file in a minute.