JPG to WebP API
POST a JPEG to /v2/convert/jpg/to/webp and get a WebP back. The API writes lossless WebP only, so the result holds exactly the decoded photo, and for photos it is bigger than the source: 85,710 bytes for the 25,110-byte sample.
- Endpoint
- POST /v2/
convert/ jpg/ to/ webp - Accepts
- jpg
- Sample timing
- 3 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/jpg/to/webp \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F File=@trailhead-sign.jpg \
-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/jpg/to/webp",
headers={"Authorization": f"Bearer {key}"},
files=[("File", open("trailhead-sign.jpg", "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 = "trailhead-sign.jpg";
form.append("File", new Blob([await readFile(file)]), file);
const url = "https://api.pdfconversionapi.com/v2/convert/jpg/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.003,
"Files": [
{
"FileName": "trailhead-sign.webp",
"FileExt": "webp",
"FileSize": 85710,
"FileData": "UklGRsZOAQBXRUJQVlA4TLpO…"
}
]
}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
- trailhead-sign.jpg (25 KB)
- Output
- trailhead-sign.webp (84 KB)
- Source
- A drawn mountain trailhead with a wooden sign and a QR code, made by us for these pages and saved at JPEG quality 92 with an EXIF block, an sRGB color profile and 300 dpi. (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 | Width of the WebP. Halving the sample to 200 × 150 gave 34,908 bytes, still more than the full-size JPEG.Values: 1–16384 |
ImageHeightinteger | Height; alone it sets the width from the 4:3 ratio, with a width it caps both.Values: 1–16384 |
ScaleProportionsboolean, default true | false stretches the photo to the exact width and height. |
ScaleIfLargerboolean, default false | true prevents upscaling; 800 px without it gave a 292,034-byte WebP. |
ScaleImageboolean, default true | false crops the requested size from the top-left corner. |
ImageInterpolationboolean, default false | Accepted either way; scaling is Lanczos3. |
ImageResolutioninteger | Accepted but not stored; the WebP has no resolution field in our output.Values: 10–800 |
ColorSpaceenum, default default | gray makes a gray WebP; the sample shrank to 47,078 bytes because the three channels become identical.Values: default, rgb, gray, monochrome; cmyk returns 400 |
TransparentColorstring | Exact-match color knock-out. On a JPEG it rarely finds whole areas: #FCFCFA, the sign panel's drawn color, matched no pixels after compression.Values: Color name, #RRGGBB or R,G,B |
FileNamestring | Output base name; default trailhead-sign.webp. |
StoreFileboolean, default false | true returns a 3-hour download URL. |
Not supported yet
Setting one of these to anything but its default returns 400.
| Option | What to do instead |
|---|---|
ImageQuality | Our WebP encoder is lossless only. ConvertAPI's default, 75, is accepted and changes nothing; ImageQuality=50 returns 400 "ImageQuality is not supported yet for JPG → WebP". |
What carries over, and what doesn't
Kept
- Every decoded pixel: the WebP decodes to exactly our decode of the JPEG
- Dimensions and aspect ratio, unless you resize
- The upright view of phone photos: a 300 × 200 test photo tagged EXIF Orientation 6 came back as a 200 × 300 WebP
- Sharp text and the QR code on the sign, with no new artifacts
Lost or not yet supported
- Size. 85,710 bytes against 25,110: lossless WebP stores the JPEG's noise and artifacts too.
- A lossy mode. There is no quality setting; for a smaller file use JPEG XL (jpg-to-jxl) or recompress the JPEG (compress-jpg).
- EXIF, the sRGB profile and the 300 dpi value.
Limits
- One JPEG per request.
- Upload size up to 10 MB on the Free plan or 100 MB on paid plans.
- 100,000,000-pixel limit per image.
- WebP output stops at 16,384 px per side. A 16,385 × 8 JPEG sent as is returned 413 "WebP images can be at most 16,384 pixels on a side, and this one is 16,385×8". When the resize causes it, as with the trailhead picture stretched to 16,385 × 100, the message ends "; lower ImageWidth or ImageHeight".
ImageWidthvalues up to 20,000 pass the parameter check, so keep them at 16,384 or less. - 120 seconds per conversion.
Where teams use it
A lossless WebP copy for editing pipelines
When later steps will crop, annotate and re-save, a lossless WebP stops the quality loss a second JPEG save would add, in a format browsers display.
One output format for mixed uploads
If your store keeps every image as WebP, JPEG uploads can join PNG ones there. Check the size first: on photos the lossless file is several times the JPEG.
Questions
Why is my WebP bigger than the JPEG?
Because it is lossless. JPEG threw information away to reach 25,110 bytes; a lossless WebP has to store the decoded result, compression artifacts included, and needed 85,710 bytes. Lossy WebP is not available from this API yet.
Is ImageQuality supported?
No. 75 is accepted because it is ConvertAPI's documented default, and the output is the same lossless file. Any other value is refused with a 400 so you know it had no effect.
How do I get a smaller file from a JPEG?
Try jpg-to-jxl (10,165 bytes for the sample at its default quality) or compress-jpg (16,411 bytes at quality 75). Resizing helps on any route.
Is there an alpha channel?
No. A WebP made from a JPEG is stored as RGB, since every pixel is opaque. TransparentColor adds an alpha channel when it matches: #F7F7F7, a light gray found on 1,880 pixels of our decode of the sample, cleared exactly those pixels and gave a 94,062-byte RGBA WebP. JPEG noise makes such exact matches scarce.
Convert your first JPG file in a minute.