PSD to PNM API
POST a .psd to /v2/convert/psd/to/pnm and get a Netpbm file back in the JSON: a 15-byte text header starting with P6, then 3 bytes per pixel. The sample scales the sticker to 200 × 150, so it holds 90,000 bytes of pixels.
- Endpoint
- POST /v2/
convert/ psd/ to/ pnm - Accepts
- psd
- Sample timing
- 93 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/psd/to/pnm \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F File=@night-market-sticker.psd \
--form-string ImageWidth=200 \
-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/psd/to/pnm",
headers={"Authorization": f"Bearer {key}"},
files=[("File", open("night-market-sticker.psd", "rb"))],
data={
"ImageWidth": "200",
},
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 = "night-market-sticker.psd";
form.append("File", new Blob([await readFile(file)]), file);
form.append("ImageWidth", "200");
const url = "https://api.pdfconversionapi.com/v2/convert/psd/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.093,
"Files": [
{
"FileName": "night-market-sticker.pnm",
"FileExt": "pnm",
"FileSize": 90015,
"FileData": "UDYKMjAwIDE1MCAyNTUK////…"
}
]
}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
- night-market-sticker.psd (173 KB)
- Output
- night-market-sticker.pnm (88 KB)
- Source
- A layered event sticker we drew for this site: 400 x 300 pixels, 8 bits per channel, transparent around the sticker, with seven layers including a soft shadow, a Screen-blended moon, a two-layer text group and a hidden draft stamp. Written as a PSD by our own script from the published file format specification. (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 | Width in pixels; the height follows. The file is exactly header plus width × height × 3 bytes: 90,015 for the sample, 360,015 at full size.Values: 1–20000 |
ImageHeightinteger | Height in pixels, or the height of a box when sent with ImageWidth.Values: 1–20000 |
ScaleProportionsboolean, default true | false stretches the sticker to the exact width and height. |
ScaleIfLargerboolean, default false | true keeps the canvas from being enlarged. |
ScaleImageboolean, default true | false crops a top-left area of the requested size. |
AlphaColorstring, default white | The color behind transparent pixels, since the file has no alpha. With black, the corners and the whole shadow came out (0, 0, 0).Values: A color name, #RRGGBB or R,G,B |
BackgroundColorstring, default white | Used when AlphaColor is not sent; BackgroundColor=black gave the same file as AlphaColor=black.Values: Same as AlphaColor |
ColorSpaceenum, default default | gray writes a one-channel graymap (P5) instead of RGB: 30,015 bytes at the sample's 200 px, 120,015 at full size. monochrome uses the same layout with only 0 and 255.Values: default, rgb, gray, monochrome; cmyk returns 400 |
FileNamestring | Base name of the file; the extension is always .pnm. |
StoreFileboolean, default false | true returns a 3-hour link in place of base64. |
What carries over, and what doesn't
Kept
- The merged layers as they would look on the background color: each pixel equals the PNG output blended onto white and rounded to 8 bits
- 8 bits per channel: the header's maximum value is 255
- The canvas size, written as width and height in the header
- Wide reader support: all three readers we tried decoded the P6 file to the same pixels
Lost or not yet supported
- Transparency. The 200 × 150 sample's 2,465 clear pixels became white (10,195 at full size), and the soft shadow became gray on white: (178, 178, 178) below the sticker at x 100, y 142, the spot that is (181, 181, 181) at full size.
- Compression, 16-bit depth, the 300 ppi setting and the color profile.
Limits
- One PSD per request.
- Requests up to 10 MB on the Free plan or 100 MB on paid plans, all files and fields together.
- Up to 100,000,000 pixels after flattening, or 413.
- Resize targets from 1 to 20,000 px per side.
- 120 seconds per conversion, then 504.
- Pixel layers, masks, groups and blend modes are tested; type layers, smart objects and layer effects are not yet.
Where teams use it
Pixels for numeric or scientific code
Netpbm files are trivial to parse: skip the text header and read a plain array. That suits test harnesses, image-processing research code and command-line tools that read PPM.
Comparing renders of a design
Convert two versions of a PSD at the same size and compare the pixel arrays directly, with no decoder in between.
Questions
Which Netpbm variant do I get?
Binary PPM, the P6 form, with a maximum value of 255 and 3 bytes per pixel. With ColorSpace=gray or monochrome you get a binary graymap (P5) with 1 byte per pixel instead. All three readers we tried opened both.
Can I keep transparency?
No. The file has no alpha channel; transparent and partly transparent pixels are blended onto AlphaColor, white by default. For pixels plus alpha, use the PNG, TIFF or BMP route.
Does ImageResolution do anything here?
It is accepted for ConvertAPI compatibility but changes nothing, because Netpbm files have no resolution field: 300 returned the same bytes as no value.
Convert your first PSD file in a minute.