PNM to TIFF API
POST a netpbm image to /v2/convert/pnm/to/tiff and get a single-page, uncompressed TIFF. RGB stays RGB, gray stays gray, and a 16-bit PPM becomes a TIFF with 16 bits per sample.
- Endpoint
- POST /v2/
convert/ pnm/ to/ tiff - 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/tiff \
-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/tiff",
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/tiff";
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.tiff",
"FileExt": "tiff",
"FileSize": 230610,
"FileData": "SUkqACSEAwABAAAAAQAAAAEA…"
}
]
}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.tiff (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 |
|---|---|
ImageResolutioninteger | DPI for the resolution tags. Without it they read 1/1 with no unit.Values: 10–800 |
ImageWidthinteger | Width after scaling; the render at 128 px wide is a 37,074-byte TIFF.Values: 1–20000 |
ImageHeightinteger | Height after scaling, or the box height when a width is also set.Values: 1–20000 |
ScaleProportionsboolean, default true | false stretches the frame to the exact box. |
ScaleIfLargerboolean, default false | true prevents enlarging a frame beyond its own size. |
ScaleImageboolean, default true | false crops a top-left region instead of resampling. |
ColorSpaceenum, default default | gray gives a one-channel 8-bit TIFF (76,998 bytes for the render); monochrome is the same size with only black and white values. On a PAM with any pixel that is not fully opaque, both keep the alpha and write an RGBA TIFF with equal red, green and blue (307,414 bytes for a 320 × 240 test file).Values: default, rgb, gray, monochrome |
TransparentColorstring | Adds an alpha channel with one exact color cleared; with any value the TIFF becomes RGBA (307,414 bytes for the render).Values: Color name, #RRGGBB or R,G,B |
ImageInterpolationboolean, default false | Accepted; resampling is Lanczos. |
FileNamestring | Base name of the .tiff; the upload's by default. |
StoreFileboolean, default false | Return a link, kept 3 hours, for large uncompressed files. |
What carries over, and what doesn't
Kept
- Every pixel: the sample TIFF's samples equal the PPM's
- 16 bits per sample: a 16-bit test PPM gave a 461,010-byte TIFF with identical values
- Gray as gray: PGM and PBM inputs give one-channel 8-bit TIFFs
- Alpha from PAM input, as a fourth sample
Lost or not yet supported
- Compression. The sample TIFF is 230,610 bytes, the same pixels as the 230,415-byte PPM plus tags.
- An explicit alpha label for PAM input: four-sample TIFFs have no ExtraSamples tag, which the TIFF 6.0 specification uses to mark alpha.
- 1-bit packing of PBM files, which become 8-bit gray.
- Header comments and odd maxvals, which are rescaled to the full range.
Limits
- One file per request; the TIFF has one page.
- Requests up to 10 MB on the Free plan or 100 MB on paid plans, all files and fields together.
- 100,000,000 pixels per image, and output files are capped at 512 MB.
- 120 seconds per conversion, then 504.
Where teams use it
Archiving captures at full bit depth
When captures are saved as 16-bit PPM, TIFF keeps all 16 bits per sample, so measurements made later on the archive match the original values.
Print and layout from netpbm output
Prepress and layout tools that take TIFF can receive frames or scans saved as PNM. Set ImageResolution so the image places at the intended physical size.
Questions
Does a 16-bit PPM stay 16-bit?
Yes. A test PPM with maxval 65535 came back with BitsPerSample 16, 16, 16 and the same values, little-endian, in a 461,010-byte TIFF.
Is the TIFF compressed?
No. There is no compression option, so the file is about as large as the pixel data: 230,610 bytes for the 320 × 240 render.
What DPI does the TIFF record?
Only what you send in ImageResolution. Netpbm headers carry no resolution, so without the parameter the tags say 1/1 with no unit.
Convert your first PNM file in a minute.