ICO to PNM API
POST an icon to /v2/convert/ico/to/pnm and get a .pnm file of raw 8-bit RGB samples. The file starts with a 15-byte P6 header, and the icon's transparent parts are blended onto white unless you set AlphaColor.
- Endpoint
- POST /v2/
convert/ ico/ to/ pnm - 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/pnm \
-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/pnm",
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/pnm";
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.pnm",
"FileExt": "pnm",
"FileSize": 196623,
"FileData": "UDYKMjU2IDI1NiAyNTUK////…"
}
]
}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.pnm (192 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 |
|---|---|
AlphaColorstring, default white | Color mixed into clear and partly clear pixels, because the output has no alpha channel. Try #202020 for icons meant for a dark theme.Values: A color name (white, black, red, green, blue, gray), #RRGGBB or R,G,B |
BackgroundColorstring, default white | Fallback name for the same setting, read only when AlphaColor isn't sent.Values: Same as AlphaColor |
ImageWidthinteger | Output width. ImageWidth=128 gave 49,167 bytes: a 15-byte header and 128 × 128 × 3 sample bytes.Values: 1–20000 |
ImageHeightinteger | Output height, or the box height when a width is also given.Values: 1–20000 |
ScaleProportionsboolean, default true | false allows distortion to fill a box of a different shape. |
ScaleIfLargerboolean, default false | true stops sizes above the icon's own from enlarging it. |
ScaleImageboolean, default true | false crops from the top-left instead of resampling. |
ColorSpaceenum, default default | gray and monochrome write a one-channel P5 graymap instead, 65,551 bytes for the sample. monochrome is cut to the values 0 and 255 after the blend onto the background.Values: default, rgb, gray, monochrome |
ImageResolutioninteger | Range-checked and not stored; netpbm headers have no resolution field.Values: 10–800 |
ImageInterpolationboolean, default false | Accepted; resizing uses a Lanczos filter in all cases. |
FileNamestring | Base name for the .pnm; defaults to the icon's name. |
StoreFileboolean, default false | Return a 3-hour link instead of base64, useful for large uncompressed files. |
What carries over, and what doesn't
Kept
- Exact color on a solid background: the sample's PNM equals the 256 px entry blended onto white, value for value
- 8 bits per channel, uncompressed: 256 × 256 × 3 = 196,608 bytes of samples after the header
- The 256 × 256 size of the largest entry
Lost or not yet supported
- Transparency. The output has no alpha channel, so clear and shadow pixels are mixed with
AlphaColor. - Compression: 196,623 bytes for one 256 px image, against 34,551 for the whole four-size .ico.
- The 16, 32 and 48 px entries.
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.
ImageWidthandImageHeightup to 20,000 px, and 100,000,000 pixels in all; at 3 bytes per pixel, large results get big.- 120 seconds per conversion, then 504.
Where teams use it
Icon pixels for scripts and models
A P6 file is a short text header followed by raw samples, so a script or a C program can read the icon without an image library. That suits hashing icons, comparing them, or feeding them to a training job.
Pipelines built around netpbm files
If your processing steps pass images along as PNM, converting icons on the way in lets them join the same pipeline. The result is the plain binary P6 form rather than PAM, so tools that only know P1 to P6 can take it.
Questions
Which netpbm variant do I get?
A binary P6 pixmap. The header is P6, then 256 256 255 on the next line, 15 bytes in all for the sample, followed by 196,608 bytes of RGB samples. With ColorSpace=gray or monochrome it is a P5 graymap with one byte per pixel. The three image readers we tried all opened the sample.
What happens to the icon's transparent corners?
They become the background color, white by default. Partly transparent shadow pixels are mixed proportionally, so the shadow fades into the background instead of ending in a hard edge.
Can the PNM keep an alpha channel?
No. The output has no alpha: P6 color or P5 gray. If you need alpha in an uncompressed file, /v2/convert/ico/to/bmp, /v2/convert/ico/to/tga and /v2/convert/ico/to/tiff keep it.
Convert your first ICO file in a minute.