ORA to PNM API
POST /v2/convert/ora/to/pnm merges the visible OpenRaster layers and writes plain 8-bit RGB samples behind a 15-byte binary PPM header (P6). Any transparency is blended onto white or AlphaColor first.
- Endpoint
- POST /v2/
convert/ ora/ to/ pnm - Accepts
- ora
- Sample timing
- 24 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/ora/to/pnm \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F File=@garden-plan.ora \
-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/ora/to/pnm",
headers={"Authorization": f"Bearer {key}"},
files=[("File", open("garden-plan.ora", "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 = "garden-plan.ora";
form.append("File", new Blob([await readFile(file)]), file);
const url = "https://api.pdfconversionapi.com/v2/convert/ora/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.024,
"Files": [
{
"FileName": "garden-plan.pnm",
"FileExt": "pnm",
"FileSize": 360015,
"FileData": "UDYKNDAwIDMwMCAyNTUK5N/P…"
}
]
}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
- garden-plan.ora (123 KB)
- Output
- garden-plan.pnm (352 KB)
- Source
- A backyard vegetable garden plan that we drew and saved as an OpenRaster file with seven layers and a layer group, including a hidden layer and a multiply layer. (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 | Output width. Size is the header plus width × height × 3; the header is 15 bytes while both sides have three digits, so 160 px wide (160 × 120) is 57,615 bytes.Values: 1–20000 |
ImageHeightinteger | Output height; 150 gives 200 × 150 and 90,015 bytes.Values: 1–20000 |
ScaleProportionsboolean, default true | false fills a box exactly, for example 200 × 200 at 120,015 bytes. |
ScaleIfLargerboolean, default false | true never enlarges past the 400 × 300 canvas. |
ScaleImageboolean, default true | false crops a top-left region of the given size. |
AlphaColorstring, default white | Backdrop for transparent canvas. The plan has an opaque paper layer, so #1B1F3B returned the identical file.Values: white, black, red, green, blue, gray, #RRGGBB or R,G,B |
BackgroundColorstring, default white | Alternative name, read when AlphaColor is not sent.Values: Same as AlphaColor |
ColorSpaceenum, default default | gray and monochrome write a one-channel PGM (P5) of 120,015 bytes, a third of the RGB size; the monochrome one holds only 0 and 255.Values: default, rgb, gray, monochrome (also srgb, truecolors, grayscale); cmyk returns 400 |
FileNamestring | Choose the name of the returned .pnm. |
StoreFileboolean, default false | Get a link valid for 3 hours rather than inline data. |
What carries over, and what doesn't
Kept
- The composited RGB values exactly, after a 15-byte header
- The 400 × 300 canvas: 360,000 bytes of pixels
- Opacity, groups and blend modes, applied before writing
Lost or not yet supported
- Alpha. A transparent canvas is blended onto the backdrop color.
- 16-bit depth from 16-bit layers; the file is MAXVAL 255.
- Layers, the 150 ppi and any color profile.
Limits
- One .ora per call, canvas up to 100,000,000 pixels, and a width or height over 300,000 px returns 422.
- Requests up to 10 MB on the Free plan or 100 MB on paid plans, all files and fields together.
- A 504 after 120 seconds.
- 8 bits per channel, uncompressed and binary: P6, or P5 for gray and monochrome.
Where teams use it
Feeding drawings to Netpbm-based tools
Image-processing scripts and teaching code that read Netpbm can take layered .ora drawings this way, with no OpenRaster parser of their own: a text header, then the pixels.
Golden files for rendering tests
A fixed header and raw pixels make byte-level comparisons simple when a test checks that a layered drawing still composites the same way.
Questions
Which Netpbm variant is it?
Binary PPM. The first line is 'P6' and the second '400 300 255', width, height and MAXVAL, and the pixel bytes follow at once, so a P6 reader opens it directly; both readers we tried did. Ask for ColorSpace=gray and you get the one-byte-per-pixel PGM variant, P5.
Does AlphaColor do anything for this file?
Only if the composite has transparent pixels. The sample plan sits on an opaque paper layer, so AlphaColor=#1B1F3B returned the same bytes as no parameter. On an .ora with an empty background, that color fills it.
Why is the PNM bigger than the .ora?
The .ora stores compressed PNG layers; Netpbm stores every pixel raw. The 126,182-byte sample becomes 360,015 bytes, 3 bytes for each of its 120,000 pixels plus the header.
Convert your first ORA file in a minute.