PSB to ICO API
POST a .psb to /v2/convert/psb/to/ico and receive an icon file with one PNG-compressed image. With no options the 3:1 banner becomes 256 × 85 pixels (13,556 bytes), the largest size that fits the 256 px icon limit.
- Endpoint
- POST /v2/
convert/ psb/ to/ ico - Accepts
- psb
- Sample timing
- 89 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/psb/to/ico \
-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/ico",
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/ico";
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.089,
"Files": [
{
"FileName": "ridge-loop-trail-banner.ico",
"FileExt": "ico",
"FileSize": 13556,
"FileData": "AAABAAEAAFUAAAAAIADeNAAA…"
}
]
}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.ico (13 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 | Icon width, with the height in proportion: 64 gave 64 × 21 and 32 gave 32 × 11 (876 bytes). Results wider than 256 are fitted back down.Values: 1–20000 |
ImageHeightinteger | Icon height. On its own, 256 first made the banner 768 × 256 and the 256 px cap then brought it to 256 × 85.Values: 1–20000 |
ScaleImageboolean, default true | false cuts a region instead of scaling: 128 × 128 from the top left kept the RIDGE LOOP label, the sky and the first marker. |
ScaleProportionsboolean, default true | false squeezes the whole banner into the exact size, 256 × 256 in 33,405 bytes. |
ScaleIfLargerboolean, default false | true prevents enlarging when the request asks for more pixels than the document has. |
ColorSpaceenum, default default | Gray or black and white icons, still stored as 32-bit PNG inside the .ico.Values: default, rgb, gray, monochrome; cmyk returns 400 |
FileNamestring | Base name of the icon file. |
StoreFileboolean, default false | true returns a 3-hour link. |
What carries over, and what doesn't
Kept
- The document's shape by default: 256 × 85 for the 384 × 128 banner
- The merged visible layers, rounded to 8 bits per channel; the hidden Old route stays out
- Opacity: every pixel of the banner icon has alpha 255
Lost or not yet supported
- Everything past 256 px per side; a document is always shrunk to fit.
- Legibility of small detail when a wide document is scaled down: at 64 × 21 the label is a few pixels high.
- More than one size per file, layers, and 16-bit depth.
Limits
- One PSB per request; one image per .ico.
- Requests up to 10 MB on the Free plan or 100 MB on paid plans, all files and fields together.
- Icons of at most 256 × 256 pixels.
- The flattened document must stay under 100,000,000 pixels, or 413.
- 120 seconds per conversion, then 504.
- Type layers, smart objects and layer effects are untested in PSB files.
Where teams use it
An icon cut from part of a large layout
A PSB may hold a whole campaign layout or a map. With ScaleImage=false and a square size you take the icon from the top-left corner of the canvas, without opening the file in an editor.
Placeholder icons in a build
When a project's artwork is a PSB, a build step can produce a temporary .ico from it until a designed icon exists. Expect a wide document to come out wide.
Questions
How do I get a square icon from a wide PSB?
Either crop with ScaleImage=false, ImageWidth=128 and ImageHeight=128, which takes the top-left 128 × 128 pixels, or stretch the whole banner with ScaleProportions=false. Cropping keeps the artwork undistorted.
Why is my icon 256 × 85?
The icon format allows at most 256 pixels per side, and the API keeps the document's 3:1 shape when it shrinks it. Ask for a square size with one of the options above.
Does it matter that the PSB is 16-bit?
Only in that the extra precision is rounded away: icons hold 8 bits per channel. The colors you see do not change otherwise.
Convert your first PSB file in a minute.