HDR to WebP API
POST a .hdr to /v2/convert/hdr/to/webp and get a lossless WebP. Pixels are decoded, clipped at 1.0, rounded to 8 bits and compressed without further loss, so decoding the WebP returns exactly those 8-bit values.
- Endpoint
- POST /v2/
convert/ hdr/ to/ webp - Accepts
- hdr
- Sample timing
- 24 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/webp \
-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/webp",
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/webp";
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.024,
"Files": [
{
"FileName": "lakeside-sunset-sky.webp",
"FileExt": "webp",
"FileSize": 38182,
"FileData": "UklGRh6VAABXRUJQVlA4TBGV…"
}
]
}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.webp (37 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 to this width; ImageWidth=120 gave a 120 × 60 preview of 8,206 bytes.Values: 1–20000 |
ImageHeightinteger | Scales to this height; ImageHeight=100 gave 200 × 100 at 17,696 bytes.Values: 1–20000 |
ScaleProportionsboolean, default true | false distorts the sky into an exact 200 × 200 box (23,242 bytes). |
ScaleIfLargerboolean, default false | true keeps 400 × 200 when more is requested; otherwise ImageWidth=800 gave 126,696 bytes. |
ScaleImageboolean, default true | false crops the top-left corner: the upper-left 200 × 100 is 4,878 bytes. |
ColorSpaceenum, default default | gray gave 12,476 bytes and monochrome just 530.Values: default, rgb, gray, monochrome; cmyk returns 400 |
TransparentColorstring | Clears one exact color and adds the alpha channel the plain file lacks; #FFFFFF clears 333 pixels, the sun's clipped core and the middle of its streak on the lake (40,846 bytes).Values: #RRGGBB, R,G,B or a color name |
FileNamestring | Base name for the WebP. |
StoreFileboolean, default false | Link for 3 hours instead of the bytes. |
What carries over, and what doesn't
Kept
- Every 8-bit value: the decoded WebP equals the BMP route's pixels exactly
- The full 400 × 200 panorama
- A small file: 38,182 bytes, against 75,292 for the 16-bit PNG
- An opaque RGB file: the alpha flag in the WebP header is off, since a Radiance sky has nothing see-through
Lost or not yet supported
- Light above 1.0: each channel is cut to 255 before encoding.
- Bits beyond 8 per channel; 13,156 distinct 16-bit colors become 9,031.
- A lossy mode.
ImageQualityhas no effect here, so the file can't be made smaller that way.
Limits
- One .hdr per request.
- Up to 10 MB on the Free plan or 100 MB on paid plans per request.
- 100,000,000 pixels.
- WebP sides of at most 16,384 px, although
ImageWidthaccepts up to 20,000: 17,000 × 100 returned 413WebP images can be at most 16,384 pixels on a side, and this one is 17,000×100; lower ImageWidth or ImageHeight. - Only
-Y … +X …Radiance files; bottom-up ones get 422. - 120 seconds per conversion.
Where teams use it
Web previews of environment maps
An HDRI download page can show each sky as a WebP preview that is smaller than both the .hdr and a PNG, with no compression artifacts.
Thumbnails in a 3D asset manager
Web front ends for asset libraries can request ImageWidth=200 to store a small lossless preview per .hdr.
Questions
Is the WebP lossy or lossless?
Always lossless (VP8L). ImageQuality=60 returned the identical file. For smaller previews, reduce the size with ImageWidth, or use hdr/to/jpg with ImageQuality.
Why is the WebP smaller than the .hdr?
The .hdr stores 4 bytes of wide-range RGBE per pixel before its run-length coding; the WebP holds 8-bit RGB after clipping, coded with a stronger lossless method. The bright range is what you give up.
Does the WebP have transparency?
No. Radiance files carry no alpha, so the WebP is plain RGB with the alpha flag in its header turned off. Only TransparentColor adds an alpha channel: #FFFFFF made the 333 white pixels clear.
Convert your first HDR file in a minute.