GIF to WebP API
Upload a GIF to POST /v2/convert/gif/to/webp and the API returns a lossless (VP8L) WebP of frame 1, transparent pixels included. The result is a still image, not an animated WebP.
- Endpoint
- POST /v2/
convert/ gif/ to/ webp - Accepts
- gif
- Sample timing
- 1 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/gif/to/webp \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F File=@parcel-tracker.gif \
-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/gif/to/webp",
headers={"Authorization": f"Bearer {key}"},
files=[("File", open("parcel-tracker.gif", "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 = "parcel-tracker.gif";
form.append("File", new Blob([await readFile(file)]), file);
const url = "https://api.pdfconversionapi.com/v2/convert/gif/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.001,
"Files": [
{
"FileName": "parcel-tracker.webp",
"FileExt": "webp",
"FileSize": 3104,
"FileData": "UklGRhgMAABXRUJQVlA4TAsM…"
}
]
}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
- parcel-tracker.gif (8.7 KB)
- Output
- parcel-tracker.webp (3.0 KB)
- Source
- A four-frame animated parcel-tracking card with a transparent outside and a QR code, drawn by us for this page. (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 the frame to this width. At 200 px the card's WebP grew to 10,628 bytes from 3,104, since resampled edges add many new colors that lossless coding has to keep.Values: 1–16384 |
ImageHeightinteger | Scales to this height (100 gave 250 × 100). With a width as well, the frame fits inside the box.Values: 1–16384 |
ScaleProportionsboolean, default true | false with both sizes distorts the card to fill them exactly. |
ScaleIfLargerboolean, default false | true means never upscale: a GIF already smaller than the target keeps its size. |
ScaleImageboolean, default true | false crops: a 150 × 100 target returned just that corner of the card, 690 bytes. |
TransparentColorstring | Also clears pixels of this exact color. With #FFFFFF the card face became transparent and the file was 3,198 bytes.Values: Color name, #RRGGBB or R,G,B |
ColorSpaceenum, default default | gray gave a 2,090-byte WebP and monochrome 1,364 bytes; both keep the clear corners. cmyk is a 400.Values: default, rgb, gray, monochrome |
ImageQualityinteger, default 75 | Accepted only at ConvertAPI's documented default of 75. The WebP is always lossless, so any other value returns 400. |
FileNamestring | Output name without extension. |
StoreFileboolean, default false | true: get a temporary URL (3 hours) instead of base64. |
Not supported yet
Setting one of these to anything but its default returns 400.
| Option | What to do instead |
|---|---|
ImageQuality | Values other than 75 return 400 "ImageQuality is not supported yet for GIF → WebP". There is no lossy WebP output. |
What carries over, and what doesn't
Kept
- Frame 1 pixel for pixel: decoded again, the WebP matched it on every channel
- Transparency, as a full alpha channel in the VP8L bitstream (its alpha flag is set)
- 400 × 160 size unless you resize
- All six card colors exactly; lossless WebP has no palette limit
Lost or not yet supported
- Animation. An animated WebP can hold frames, but this route writes one: the tracker's Shipped, Out for delivery and Delivered steps are dropped.
- Frame timing and loop count.
- The GIF's key color under transparent pixels is kept as is (magenta in the sample), invisible unless alpha is ignored.
- No lossy option: photos inside GIFs stay lossless and can be larger than a lossy WebP would be.
Limits
- One file per call, and only frame 1 of it.
- Requests up to 10 MB on the Free plan or 100 MB on paid plans, all files and fields together.
- Images over 100,000,000 pixels get a 413 before decoding.
- 16,384 px per side is the most a WebP holds.
ImageWidthandImageHeightaccept up to 20,000, but the card 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"; at 16,384 × 100 it gave a 549,584-byte file. A 16,385 × 2 GIF sent with no size gets 413 "WebP images can be at most 16,384 pixels on a side, and this one is 16,385×2", without the hint. - 120-second limit per conversion (504 after that).
Where teams use it
Lighter stills for web pages
When only the first frame matters, such as a product badge or a status card, the lossless WebP is about a third the size of the source GIF in our sample and still has a clear background.
Asset pipelines standardizing on WebP
Accept GIFs from content editors and store a WebP rendition next to the other WebP images your pages already serve.
Questions
Will an animated GIF become an animated WebP?
No. The output is a single-frame lossless WebP of the GIF's first frame, returned with a 200 status. Keep the GIF if the motion matters.
How much smaller is the WebP?
For the sample, 3,104 bytes against 8,956 for the GIF. Part of that is the three dropped frames; frame 1 alone as a GIF from our API is 3,388 bytes, still a little larger than the WebP.
Can I ask for lossy WebP with ImageQuality?
Not yet. Only the value 75 is accepted, because that is ConvertAPI's documented default; anything else gets a 400 so you never receive a file that ignored your setting.
Is the transparency kept?
Yes. The clear corners of the card come through with alpha 0, and the opaque pixels with alpha 255.
Convert your first GIF file in a minute.