HEIC to WebP API
POST a HEIC to /v2/convert/heic/to/webp and get a lossless WebP (VP8L) of the decoded, upright photo. Lossless keeps every pixel but costs bytes: the sample at the 320 px it asks for is 101,832 bytes, 4.5 times the 22,517-byte HEIC.
- Endpoint
- POST /v2/
convert/ heic/ to/ webp - Accepts
- heic
- Sample timing
- 28 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/heic/to/webp \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F File=@harbor-dusk.heic \
--form-string ImageWidth=320 \
-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/heic/to/webp",
headers={"Authorization": f"Bearer {key}"},
files=[("File", open("harbor-dusk.heic", "rb"))],
data={
"ImageWidth": "320",
},
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 = "harbor-dusk.heic";
form.append("File", new Blob([await readFile(file)]), file);
form.append("ImageWidth", "320");
const url = "https://api.pdfconversionapi.com/v2/convert/heic/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.028,
"Files": [
{
"FileName": "harbor-dusk.webp",
"FileExt": "webp",
"FileSize": 101832,
"FileData": "UklGRsCNAQBXRUJQVlA4TLON…"
}
]
}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
- harbor-dusk.heic (22 KB)
- Output
- harbor-dusk.webp (99 KB)
- Source
- A harbor at dusk that we drew ourselves, saved as a 400 x 300 HEIC stored sideways with a rotation flag and made-up camera and GPS tags. (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 | Output width. The sample's 320 gives 320 × 240 and 101,832 bytes; full size is 145,596 bytes and 200 px is 42,588.Values: 1–16384 |
ImageHeightinteger | Output height, or a box together with ImageWidth.Values: 1–16384 |
ScaleProportionsboolean, default true | false lets both sizes distort the photo. |
ScaleIfLargerboolean, default false | true never enlarges. |
ScaleImageboolean, default true | false crops the top-left area. |
ColorSpaceenum, default default | At the sample's 320 px, gray gives 42,148 bytes against 101,832 and monochrome 968. At full size they give 69,368 and 1,456 bytes against 145,596.Values: default, rgb, gray, monochrome |
TransparentColorstring | Adds an alpha channel and clears pixels of exactly this color in it. At full size 13,32,62 cleared 718 pixels and made the file 159,546 bytes; at the sample's 320 px it clears 408 (111,146 bytes).Values: #RRGGBB, R,G,B or a basic color name |
ImageResolutioninteger | Accepted for ConvertAPI compatibility; WebP has no resolution field, so nothing changes.Values: 10–800 |
FileNamestring | Base name of the .webp. |
StoreFileboolean, default false | true returns a link valid for 3 hours. |
Not supported yet
Setting one of these to anything but its default returns 400.
| Option | What to do instead |
|---|---|
ImageQuality | Output is always lossless. 75, ConvertAPI's default, is accepted; any other value returns 400. |
What carries over, and what doesn't
Kept
- Every decoded pixel: the WebP matched our PNG of the same HEIC at the same size, with no difference anywhere
- The upright view of the sideways-stored sample
- An opaque photo stays opaque: the WebP is RGB with no alpha channel unless
TransparentColorasks for one
Lost or not yet supported
- Size against the HEIC. HEIC's lossy compression is far more compact than lossless WebP: 22,517 bytes became 145,596 at full size.
- EXIF, XMP and the color profile: the file holds a single VP8L chunk and nothing else.
- The choice of lossy WebP.
ImageQualityis refused with 400. - Tones of HEIC files with an embedded Display P3 profile: gray 16 became 0 in our test.
Limits
- One HEIC per request; at most 100,000,000 pixels.
- 16,384 px per side at most in the WebP. The harbor picture stretched to 16,385 × 100 returned 413
WebP images can be at most 16,384 pixels on a side, and this one is 16,385×100; lower ImageWidth or ImageHeight, and 16,384 × 100 converted to 917,674 bytes.ImageWidthandImageHeightaccept up to 20,000, so larger values are refused only after the resize. - Requests up to 10 MB on the Free plan or 100 MB on paid plans, and 120 seconds per conversion.
- A HEIC saved at a desktop encoder's top quality setting returned 422 'the input file could not be read: the HEIC image is damaged, or encoded in a way that can't be read yet'.
Where teams use it
Web display of phone uploads
Chrome, Edge and Firefox don't display HEIC. For sites that serve WebP, converting at the display width, as the sample does with ImageWidth=320, keeps a lossless file at a size a page can carry.
A lossless working copy
WebP lossless stores the decoded photo exactly in fewer bytes than PNG: 145,596 against 195,656 for the sample at full size.
Questions
Why is the WebP bigger than the HEIC?
The WebP is lossless: it stores the decoded photo exactly, while the HEIC was compressed with loss. Scaling down helps most: the sample is 145,596 bytes at 400 px and 101,832 at 320.
Can I get a lossy WebP?
Not on this route yet. ImageQuality=50 returns 400 'ImageQuality is not supported yet for HEIC → WebP'. For a small lossy file, heic/to/jpg takes a quality setting.
Does the WebP keep the photo's GPS location?
No. The sample's EXIF, with a made-up camera and GPS position, is not copied; the WebP contains only the image chunk.
Is a .heif file handled the same way?
Use /v2/convert/heif/to/webp for .heif files. That route doesn't refuse ImageQuality; it accepts and ignores it, so the output is lossless there too.
Convert your first HEIC file in a minute.