HDR to ICO API
POST a .hdr to /v2/convert/hdr/to/ico and get an icon holding one PNG-compressed 32-bit image. The 400 × 200 sky was scaled to fit 256 × 256 and came back as a 256 × 128 icon of 19,590 bytes.
- Endpoint
- POST /v2/
convert/ hdr/ to/ ico - Accepts
- hdr
- Sample timing
- 26 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/hdr/to/ico \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F File=@lakeside-sunset-sky.hdr \
-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/hdr/to/ico",
headers={"Authorization": f"Bearer {key}"},
files=[("File", open("lakeside-sunset-sky.hdr", "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 = "lakeside-sunset-sky.hdr";
form.append("File", new Blob([await readFile(file)]), file);
const url = "https://api.pdfconversionapi.com/v2/convert/hdr/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.026,
"Files": [
{
"FileName": "lakeside-sunset-sky.ico",
"FileExt": "ico",
"FileSize": 19590,
"FileData": "AAABAAEAAIAAAAAAIABwTAAA…"
}
]
}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
- lakeside-sunset-sky.hdr (90 KB)
- Output
- lakeside-sunset-sky.ico (19 KB)
- Source
- A sunset sky panorama we drew in code and saved as a 400 × 200 Radiance RGBE file with run-length scanlines; the sun disc is 60.0. (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 | Scales first; ImageWidth=64 gave a 64 × 32 icon of 2,450 bytes. Anything over 256 still ends at 256 × 128.Values: 1–20000 |
ImageHeightinteger | Scales by height: ImageHeight=100 gave 200 × 100.Values: 1–20000 |
ScaleProportionsboolean, default true | false and a 200 × 200 box give a square 200 × 200 icon, the sky squeezed sideways. |
ScaleIfLargerboolean, default false | true prevents enlarging past the source; the 256 px cap applies regardless. |
ScaleImageboolean, default true | false crops the top left at the requested size, 4,669 bytes for the upper-left 200 × 100. |
ColorSpaceenum, default default | gray gave an 18,133-byte gray icon and monochrome a 1,414-byte one in pure black and white, since the threshold is applied again after the scaling to 256 px.Values: default, rgb, gray, monochrome; cmyk returns 400 |
FileNamestring | Name of the icon file. |
StoreFileboolean, default false | Link instead of data, kept for 3 hours. |
What carries over, and what doesn't
Kept
- The 2:1 shape: 400 × 200 became 256 × 128
- Opaque pixels throughout, as the Radiance file has no alpha
- 8-bit color, stored as a PNG inside the icon
Lost or not yet supported
- Every icon size but one; the file has a single entry.
- Square proportions, unless you crop or stretch.
- Brightness above 1.0 and all HDR range.
- Detail: the panorama shrinks to 256 px across.
Limits
- One .hdr per call; the icon is at most 256 × 256.
- Request size up to 10 MB on the Free plan or 100 MB on paid plans.
- Input images up to 100,000,000 pixels.
- Only top-down RGBE files are read.
- 120 seconds per request.
Where teams use it
File icons for an HDRI library
A desktop tool that manages environment maps can give each .hdr a small icon of its own sky, made by one request per file.
Square icons from a panorama crop
Send ImageWidth=128, ImageHeight=128 and ScaleImage=false to cut a 128 × 128 square from the top-left corner instead of shrinking the whole 2:1 frame.
Questions
Why is the icon 256 × 128?
Icons are capped at 256 × 256, and the image is fitted inside that box keeping its 2:1 shape. Use ScaleProportions=false with a square size, or crop with ScaleImage=false, for a square icon.
Does the icon contain several sizes?
No, one. The directory lists a single 32-bit entry stored as PNG. Request each size you need separately.
Do icon sizes above 256 px work?
No. ImageWidth=800 still returned a 256 × 128 icon, because the cap is applied after resizing.
Convert your first HDR file in a minute.