PPS to JPG API
POST /v2/convert/pps/to/jpg takes a .pps show file, the kind that starts its slide show when opened, and returns one JPEG per visible slide. The sample's 16:10 lobby loop, sent with ImageResolution=120, gives four 1,200 × 750 JPEGs, 276,212 bytes in all.
- Endpoint
- POST /v2/
convert/ pps/ to/ jpg - Accepts
- pps
- Sample timing
- 0.582 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/pps/to/jpg \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F File=@riverside-lobby-loop.pps \
--form-string ImageResolution=120 \
-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/pps/to/jpg",
headers={"Authorization": f"Bearer {key}"},
files=[("File", open("riverside-lobby-loop.pps", "rb"))],
data={
"ImageResolution": "120",
},
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 = "riverside-lobby-loop.pps";
form.append("File", new Blob([await readFile(file)]), file);
form.append("ImageResolution", "120");
const url = "https://api.pdfconversionapi.com/v2/convert/pps/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.582,
"Files": [
{
"FileName": "riverside-lobby-loop-1.jpg",
"FileExt": "jpg",
"FileSize": 72345,
"FileData": "/9j/4AAQSkZJRgABAgEAeAB4…"
},
{
"FileName": "riverside-lobby-loop-2.jpg",
"FileExt": "jpg",
"FileSize": 69627,
"FileData": "/9j/4AAQSkZJRgABAgEAeAB4…"
},
{
"FileName": "riverside-lobby-loop-3.jpg",
"FileExt": "jpg",
"FileSize": 85063,
"FileData": "/9j/4AAQSkZJRgABAgEAeAB4…"
},
… 1 more files
]
}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
- riverside-lobby-loop.pps (75 KB)
- Output
- riverside-lobby-loop-1.jpg (71 KB)riverside-lobby-loop-2.jpg (68 KB)riverside-lobby-loop-3.jpg (83 KB)riverside-lobby-loop-4.jpg (48 KB)
- Source
- A five-slide self-running lobby show for a fictional clock museum, made by us and saved as a PowerPoint 97-2003 show with fades and 8-second timings; slide 4 is hidden. (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, default 150 | Pixels per inch of slide, stored in the JPEG as its DPI when you send it. The sample's 10 × 6.25 in slides are 1,500 × 937 at the default and 1,200 × 750 at 120.Values: 36–600 |
ImageQualityinteger, default 85 | JPEG quality: at 120 DPI the four slides take 276,212 bytes at 85 and 207,088 at 60.Values: 1–100 |
ImageHeightinteger | Height in pixels. ImageHeight=1080 gives 1,727 × 1,080, because the slides are 16:10, not 16:9. The width follows the slide's exact 10 × 6.25 in shape, so it was 1,727 at 96, 150 and 300 DPI alike.Values: 1–20000 |
ImageWidthinteger | Width in pixels, or with ImageHeight a box: 1,920 × 1,080 also returned 1,727 × 1,080, the slide fitted inside with its 16:10 shape.Values: 1–20000 |
PageRangestring | Visible slides to draw; the hidden staff slide is not counted, so the gift shop slide is number 4.Values: e.g. 1, 2-3 |
ConvertHiddenSlidesboolean, default false | true adds the hidden staff checklist as -4.jpg and moves the gift shop to -5.jpg. |
Passwordstring | Accepted, but encrypted shows can't be opened yet: a .pps we locked in PowerPoint returned 422 "the input file is password-protected" even with the right password. A show without a password ignores it. |
What carries over, and what doesn't
Kept
- Each visible slide of the show as its own image, in show order
- The 16:10 proportions of the slides at any width or height you ask for, unless
ScaleProportions=falsestretches them to fill both - The paper-colored background, the clock picture, the tour table with its brass header and the rounded banner
- Sharp text and shapes at high resolution, since they are drawn as vectors; only the 400 × 400 clock picture has a fixed pixel count
Lost or not yet supported
- Everything that moves: the fades between slides, the 8-second auto-advance and the loop setting are not part of a still image.
- The hidden staff slide and the speaker note on slide 3, by default.
- Calibri, drawn with the metric-compatible Carlito, and Arial, drawn with Liberation Sans.
- A 16:9 frame. The API doesn't pad slides to a screen shape: 16:10 slides stay 16:10, or are distorted if you send
ScaleProportions=falsewith both sizes.
Limits
- One .pps per request, at most 2,000 slides drawn.
- 16,000 pixels per side and 100 million pixels per image.
- Requests up to 10 MB on the Free plan or 100 MB on paid plans; 120 seconds per conversion, then 504.
- Office options this route can't apply return 400, for example "ConvertSpeakerNotes is not supported yet for PPS files".
- Password-protected shows can't be converted yet: 422 "the input file is password-protected", even with the right
Password.
Where teams use it
Moving a lobby or kiosk show to a new screen system
If your new signage player takes an image playlist but not a .pps, convert the show to JPEGs, give each the 8 seconds the original used, and the loop plays again.
Posting event slides online
A looping show from an event may hold the schedule or the sponsor slides people ask for afterwards. As JPEGs they can go straight into a post or a web page.
Questions
What is the difference between .pps and .ppt here?
The binary format is the same; the .pps extension makes PowerPoint start the slide show when it opens the file. This route reads it the same way, and a .ppt renamed to .pps converts too. For newer .ppsx shows, use /v2/convert/ppsx/to/jpg.
Can I get the slide timings with the images?
Not from this route; the response has only the images. To read the timings and transitions, convert the show with /v2/convert/pps/to/pptx: each slide there keeps its advance time, 8,000 ms in the sample.
How do I make Full HD images for a 1,920 × 1,080 screen?
Send ImageWidth=1920 and ImageHeight=1080. A 16:10 slide is fitted inside at 1,727 × 1,080 with its proportions kept; add the side bars in your player if it needs exactly 1,920 pixels.
Why four images from a five-slide show?
Slide 4 of the sample, a staff checklist, is hidden. Hidden slides are skipped unless you send ConvertHiddenSlides=true.
Convert your first PPS file in a minute.