PNM to PNM API
POST a netpbm image to /v2/convert/pnm/to/pnm and get a binary P6 pixmap with three 8-bit channels. ASCII, bitmap, graymap and 16-bit inputs all come out in that layout, or as a P5 graymap with ColorSpace=gray, and ImageWidth or ImageHeight resizes on the way.
- Endpoint
- POST /v2/
convert/ pnm/ to/ pnm - Accepts
- pnm
- Sample timing
- under 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/pnm/to/pnm \
-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/pnm",
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/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,
"Files": [
{
"FileName": "three-spheres-render.pnm",
"FileExt": "pnm",
"FileSize": 230415,
"FileData": "UDYKMzIwIDI0MCAyNTUK0OP7…"
}
]
}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.pnm (225 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 | Resize to this width. The render at ImageWidth=128 is a 128 × 96 file of 36,878 bytes.Values: 1–20000 |
ImageHeightinteger | Resize to this height; ImageHeight=100 gives 133 × 100 (39,915 bytes).Values: 1–20000 |
ScaleProportionsboolean, default true | With false, ImageWidth=200 and ImageHeight=200 give a 200 × 200 file instead of 200 × 150. |
ScaleIfLargerboolean, default false | true stops enlargement, so a 640 px request leaves the 320 px render at 230,415 bytes instead of 921,615. |
ScaleImageboolean, default true | false crops a region at 1:1 from the top-left. |
AlphaColorstring, default white | Blend color for PAM inputs that have an alpha channel, since the output has none.Values: A color name, #RRGGBB or R,G,B |
BackgroundColorstring, default white | Read in place of AlphaColor when that one isn't sent.Values: Same as AlphaColor |
ColorSpaceenum, default default | gray and monochrome write a one-channel P5 graymap, 76,815 bytes for the render; monochrome holds only 0 and 255. rgb gives the same P6 as the default.Values: default, rgb, gray, monochrome |
ImageResolutioninteger | Accepted, not stored: netpbm headers have no DPI.Values: 10–800 |
ImageInterpolationboolean, default false | Accepted; resizing is Lanczos either way. |
FileNamestring | Output name; .pnm is added. Defaults to the uploaded file's name. |
StoreFileboolean, default false | Return a link instead of base64 for large files, kept 3 hours. |
What carries over, and what doesn't
Kept
- Pixel bytes: the sample's 230,400 bytes of RGB samples came back unchanged, and the file is still 230,415 bytes; only the line break before
255in the header became a space - 8-bit values of 16-bit files: a 16-bit PPM made from the render gave exactly the same output as the 8-bit render
- Width and height, unless you resize
Lost or not yet supported
- Compact gray and bitmap storage by default. A 76,815-byte PGM became a 230,415-byte P6 file, and a 9,611-byte PBM the same;
ColorSpace=grayormonochromebrings them back to one byte per pixel, but there is no 1-bit P4 output. - 16-bit precision, alpha channels and header comments.
- ASCII output; every result is binary.
Limits
- One file per request.
- Requests up to 10 MB on the Free plan or 100 MB on paid plans, all files and fields together.
- 100,000,000 pixels, checked from the header and again after resizing.
- 120 seconds per conversion, then 504.
Where teams use it
One layout for a mixed netpbm collection
Archives of PBM, PGM and PPM files, some ASCII and some binary, can be rewritten to one binary RGB form so downstream code reads a single layout. A 60 × 40 ASCII P3 test file, cut from the render with one row of samples per line, shrank from 28,813 to 7,213 bytes this way.
Resizing without leaving netpbm
Programs that read raw RGB samples can get a scaled copy in the same family: the render at 128 px wide is 36,878 bytes and needs no decoder.
Questions
Is the output P6 or PAM?
P6, the plain binary pixmap, and P5 when you send ColorSpace=gray or monochrome. The sample's header went from P6, 320 240 and 255 on three lines to P6 and 320 240 255 on two, still 15 bytes, and the samples after it are the input's, byte for byte.
Why did my PGM get three times bigger?
Without ColorSpace, the output has red, green and blue channels, so a one-channel graymap is written with three equal samples per pixel. Send ColorSpace=gray for a P5 graymap: the 76,815-byte test PGM came back at 76,815 bytes with the same gray values.
Can I get ASCII P3 or a 16-bit file back?
No. The result is always binary with MAXVAL 255.
Convert your first PNM file in a minute.