PSB to GIF API
Send a .psb to /v2/convert/psb/to/gif and the response carries a single-frame GIF. The 16-bit banner sample, 1,421 colors once rounded to 8 bits, came back with 154 colors on 171 palette entries in 8,305 bytes.
- Endpoint
- POST /v2/
convert/ psb/ to/ gif - Accepts
- psb
- Sample timing
- 0.117 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/psb/to/gif \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F File=@ridge-loop-trail-banner.psb \
-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/psb/to/gif",
headers={"Authorization": f"Bearer {key}"},
files=[("File", open("ridge-loop-trail-banner.psb", "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 = "ridge-loop-trail-banner.psb";
form.append("File", new Blob([await readFile(file)]), file);
const url = "https://api.pdfconversionapi.com/v2/convert/psb/to/gif";
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.117,
"Files": [
{
"FileName": "ridge-loop-trail-banner.gif",
"FileExt": "gif",
"FileSize": 8305,
"FileData": "R0lGODlhgAGAAIAAAAAAAAAA…"
}
]
}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
- ridge-loop-trail-banner.psb (171 KB)
- Output
- ridge-loop-trail-banner.gif (8.1 KB)
- Source
- A layered trail-map banner we drew for this site: 384 x 128 pixels at 16 bits per channel on an opaque Background layer, with seven layers including a smooth sky gradient and a hidden old route. Written in the large document (PSB) format 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 | Resizes before the palette is chosen. 192 made a 192 × 64 GIF of 4,732 bytes; 768 one of 28,228 bytes.Values: 1–20000 |
ImageHeightinteger | Resizes by height; both values together give a box the banner fits in.Values: 1–20000 |
ScaleProportionsboolean, default true | false fills the exact width and height. |
ScaleIfLargerboolean, default false | true skips the resize when it would enlarge the banner. |
ScaleImageboolean, default true | false crops the top-left area instead. |
TransparentColorstring | Gives the GIF a transparent palette entry for one exact color. #FFFFFF cleared only the 295 pure-white pixels at the core of the label and markers; their anti-aliased edges stay opaque (8,496 bytes).Values: #RRGGBB, R,G,B or a color name |
ColorSpaceenum, default default | gray gave 8,900 bytes; monochrome reduced the banner to black and white in 947 bytes.Values: default, rgb, gray, monochrome; cmyk returns 400 |
FileNamestring | Base name of the result, defaulting to the upload's. |
StoreFileboolean, default false | true puts the GIF behind a 3-hour link. |
What carries over, and what doesn't
Kept
- Flat fills exactly: all 12,249 pixels in the far ridge's color and all 11,989 in the near ridge's come through unchanged
- Overall accuracy: 0.18 levels of average difference from the 8-bit result; only 244 of 49,152 pixels moved more than 8 levels
- The layers that are switched on; the hidden Old route stays out
- Opacity: the banner has no transparency and the GIF declares none
Lost or not yet supported
- Smooth gradients. In one column of the sky, 80 distinct shades became 67, so the gradient shows steps.
- Color count: 1,421 colors (8-bit) or 2,056 (16-bit) go into 171 palette entries, which hold 154 distinct colors.
- 16-bit depth, the 150 ppi setting, layers and any color profile.
Limits
- One PSB per request; one frame per GIF.
- Requests up to 10 MB on the Free plan or 100 MB on paid plans, all files and fields together.
- At most 100,000,000 pixels after flattening, or 413.
- Resize targets from 1 to 20,000 px per side.
- GIF sides stop at 65,535 px. A 70,000 × 2 test PSB got 413 'GIF images can be at most 65,535 pixels on a side'; with
ImageWidth=20000it converted. - 120 seconds per conversion, then 504.
- Our PSB tests cover pixel layers, masks, groups and blend modes only.
Where teams use it
Quick previews where only GIF is accepted
Older ticket systems, wikis and mail templates may show GIF but not PNG or WebP. A scaled GIF of a large PSB gives reviewers a look without downloading the layered document.
Flat maps and diagrams
Artwork built from a few flat fills, like the sample's ridges and trail, survives a 256-color palette with small differences; set ImageWidth to the display size first.
Questions
How many colors will my GIF have?
Up to 256, picked from the merged image. The sample banner uses 171 palette entries, but some repeat a color, so it shows 154. Smooth gradients lose shades first; the banner's flat ridge fills came through unchanged, and most larger errors sit on the edges of the dashed trail and markers.
Does ImageWidth help the GIF look better?
It decides which pixels the palette is built from. Scaling a large PSB to its display width first means the 256 colors are spent on what people will see, and the file shrinks: 4,732 bytes at 192 px wide against 8,305 at 384.
Are options that this route ignores reported?
No. ConvertAPI documents no options for PSB to GIF, so anything the route does not read, such as ImageQuality, is ignored without an error.
Convert your first PSB file in a minute.