NUMBERS to JPG API
POST a .numbers file to /v2/convert/numbers/to/jpg and every printed page returns as a JPEG. Sent with no options, the sample is laid out on A4 portrait and comes back as two 1240 × 1753 images, three columns on each.
- Endpoint
- POST /v2/
convert/ numbers/ to/ jpg - Accepts
- numbers
- Sample timing
- 0.613 s 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/numbers/to/jpg \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F File=@account-statement.numbers \
-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/numbers/to/jpg",
headers={"Authorization": f"Bearer {key}"},
files=[("File", open("account-statement.numbers", "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 = "account-statement.numbers";
form.append("File", new Blob([await readFile(file)]), file);
const url = "https://api.pdfconversionapi.com/v2/convert/numbers/to/jpg";
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.613,
"Files": [
{
"FileName": "account-statement-1.jpg",
"FileExt": "jpg",
"FileSize": 85027,
"FileData": "/9j/4AAQSkZJRgABAgAAAQAB…"
},
{
"FileName": "account-statement-2.jpg",
"FileExt": "jpg",
"FileSize": 78674,
"FileData": "/9j/4AAQSkZJRgABAgAAAQAB…"
}
]
}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
- account-statement.numbers (102 KB)
- Output
- account-statement-1.jpg (83 KB)account-statement-2.jpg (77 KB)
- Source
- A three-row account statement table saved by Numbers 12, taken from the MIT-licensed test files of an open-source Numbers reader; not made by us (MIT). 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 |
|---|---|
ImageQualityinteger, default 85 | JPEG quality. The sample's pages are 85,027 and 78,674 bytes at the default.Values: 1–100 |
ImageResolutioninteger, default 150 | Pixels per inch of paper; recorded in the JFIF header only when you send it.Values: 36–600 |
PageOrientationenum, default default | landscape puts the sample's six columns on one JPEG instead of two.Values: default, portrait, landscape |
FitToPageboolean, default false | true scales the table to one page wide. |
PageSizeenum, default default | Paper size; A4 when left out.Values: letter, legal, tabloid, ledger, statement, executive, lettersmall, a3, a4, a4small, a5, b4, b5 |
Scaleinteger | Print percentage.Values: 10–300 |
WorksheetNamestring | Print one sheet by the name the printout uses, which joins sheet and table: Account_Statement for the sample (any case). Account alone returns 400. |
WorksheetIndexinteger | Print one sheet by position. The sample has one, so 2 returns 400 saying the workbook has 1 worksheet.Values: 1 to the sheet count |
PageRangestring | Only these pages; PageRange=1 returns the Date, Transaction Details and Paid In columns of the sample.Values: e.g. 1 |
ImageWidthinteger | Width of each JPEG.Values: 1–20000 |
ImageHeightinteger | Height of each JPEG.Values: 1–20000 |
BackgroundColorstring, default white | Paper color.Values: a color name, #RRGGBB or R,G,B |
ColorSpaceenum, default default | gray for a one-channel JPEG.Values: rgb, gray, monochrome |
FileNamestring | Stem for the page files. |
StoreFileboolean, default false | Links valid for 3 hours. |
Not supported yet
Setting one of these to anything but its default returns 400.
Refused when set: AutoFitColumns, ThousandsSeparator, DecimalSeparator, DateFormat.
What carries over, and what doesn't
Kept
- Both halves of the table, on two pages: Date to Paid In, then Withdrawn to Category
- Formatted dates (May 1, 2020) and the currency code on amounts
- The table's header shading and row banding
- A sans-serif header (Account_Statement) and footer (Page 1, Page 2), as no page option was sent
Lost or not yet supported
- A one-page view of the table: with no options its columns are split across two JPEGs.
- Decimal places on currency cells (2018.51 prints as 2019GBP).
- Exact pixels, as JPEG compression is lossy.
- The original fonts.
Limits
- One Numbers file per request.
- Requests up to 10 MB on the Free plan or 100 MB on paid plans, all files and fields together.
- Up to 2,000 page images per request.
- A page image can't exceed 16,000 pixels per side or 100,000,000 pixels (413).
- 120 seconds per conversion, then 504.
Where teams use it
Sharing a statement by message
Messaging apps and email show JPEGs inline. Send a picture of the table to someone who doesn't have Numbers.
Thumbnails in a document list
Make a small JPEG of page 1 for each uploaded .numbers file with PageRange=1 and ImageWidth.
Checking how a table prints
See where a wide Numbers table breaks across pages before you print it or turn it into a PDF.
Questions
Why did I get two JPEGs for one small table?
The table is wider than A4 portrait, so it is printed in two column groups, one per page. Ask for PageOrientation=landscape or FitToPage=true to get a single JPEG.
What is the Account_Statement text at the top?
The page header prints the sheet's name. For printing, the sheet (Account) and its table (Statement) are joined into one name, Account_Statement. Use that name with WorksheetName on this route; Account alone returns 400.
Can I make the JPEGs smaller?
Lower ImageQuality (85 by default), lower ImageResolution (150 by default), or set ImageWidth. Fitting the table on one page with FitToPage=true also halves the number of files for the sample.
Convert your first NUMBERS file in a minute.