PNM to ICO API
POST a netpbm image to /v2/convert/pnm/to/ico and get an icon with a single 32-bit entry. Anything wider or taller than 256 px is scaled down to fit: the 320 × 240 sample became a 256 × 192 icon of 33,785 bytes.
- Endpoint
- POST /v2/
convert/ pnm/ to/ ico - Accepts
- pnm
- Sample timing
- 3 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/pnm/to/ico \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F File=@three-spheres-render.ppm \
-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/pnm/to/ico",
headers={"Authorization": f"Bearer {key}"},
files=[("File", open("three-spheres-render.ppm", "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 = "three-spheres-render.ppm";
form.append("File", new Blob([await readFile(file)]), file);
const url = "https://api.pdfconversionapi.com/v2/convert/pnm/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.003,
"Files": [
{
"FileName": "three-spheres-render.ico",
"FileExt": "ico",
"FileSize": 33785,
"FileData": "AAABAAEAAMAAAAAAIADjgwAA…"
}
]
}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
- three-spheres-render.ppm (225 KB)
- Output
- three-spheres-render.ico (33 KB)
- Source
- A 320 x 240 frame from a small ray tracer we wrote for this page (three spheres on a checkered floor), saved as a binary PPM (P6) with 8 bits per channel. (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 | Icon width. ImageWidth=48 gives a 48 × 36 icon of 3,010 bytes from the render. Widths over 256 are accepted, then fitted back to 256.Values: 1–20000 |
ImageHeightinteger | Icon height; with a width, the image is fitted inside that box.Values: 1–20000 |
ScaleProportionsboolean, default true | Set false with ImageWidth=256 and ImageHeight=256 to squeeze a 4:3 frame into a square icon (43,370 bytes, distorted). |
ScaleIfLargerboolean, default false | true stops small images from being enlarged before they go into the icon. |
ScaleImageboolean, default true | false crops instead: ImageWidth=240, ImageHeight=240 and ScaleImage=false cut a square 240 px icon from the top-left of the frame. |
ColorSpaceenum, default default | Gray (33,016 bytes) or two-tone (2,037 bytes) icons; the entry stays 32-bit RGBA. monochrome is applied again after the fit to 256 px, so the icon holds only black and white.Values: default, rgb, gray, monochrome |
ImageResolutioninteger | Accepted and dropped; icons have no DPI field.Values: 10–800 |
ImageInterpolationboolean, default false | Accepted; the downscale to 256 px and any resize use Lanczos. |
FileNamestring | Name of the .ico; defaults to the uploaded file's name. |
StoreFileboolean, default false | Return a link to the icon, kept for 3 hours. |
What carries over, and what doesn't
Kept
- The aspect ratio: 320 × 240 became 256 × 192, not a stretched square
- Alpha from PAM files: a test P7 file's transparent rows stayed transparent in the icon
- Full 8-bit color in a 32-bit PNG-compressed entry
Lost or not yet supported
- Detail, when the image is larger than 256 px: the frame is scaled down with a Lanczos filter.
- A square shape. The icon is not padded, so wide or tall images give wide or tall icons.
- Extra sizes. The .ico holds one entry, without 16, 32 or 48 px versions.
- 16-bit depth, reduced to 8 bits per channel.
Limits
- One file per request.
- The icon entry is at most 256 × 256.
- Requests up to 10 MB on the Free plan or 100 MB on paid plans, all files and fields together.
- 120 seconds per conversion, then 504.
Where teams use it
App icons from generated artwork
Programs that draw their own artwork, such as a render, a plot or a procedural logo, can write it as PPM and turn it into a Windows icon in one request without an image library on their side.
Icons with transparency from PAM
A P7 file with an RGB_ALPHA tuple type carries an alpha channel, and the icon keeps it, so shaped icons with clear backgrounds can come straight out of a netpbm pipeline.
Questions
Why is my icon not square?
The image is fitted into 256 × 256 with its proportions kept, and nothing is added around it. Crop to a square first, use ScaleImage=false with equal width and height, or stretch it with ScaleProportions=false.
Can I get 16, 32, 48 and 256 px in one icon?
No. One request produces one entry. You can make each size with ImageWidth, but combining them into a single file has to happen on your side.
What is the largest icon I can make?
256 × 256, the largest size an icon entry can hold. Bigger requests are scaled to your size and then fitted back to 256.
Convert your first PNM file in a minute.