WebP to GIF API
POST a WebP to /v2/convert/webp/to/gif and get a one-frame GIF with up to 256 colors. Pixels that are fully transparent in the WebP stay transparent, but GIF has no partial transparency, and animated WebPs give their first frame only.
- Endpoint
- POST /v2/
convert/ webp/ to/ gif - Accepts
- webp
- Sample timing
- 64 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/webp/to/gif \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F File=@guest-badge.webp \
-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/webp/to/gif",
headers={"Authorization": f"Bearer {key}"},
files=[("File", open("guest-badge.webp", "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 = "guest-badge.webp";
form.append("File", new Blob([await readFile(file)]), file);
const url = "https://api.pdfconversionapi.com/v2/convert/webp/to/gif";
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.064,
"Files": [
{
"FileName": "guest-badge.gif",
"FileExt": "gif",
"FileSize": 24673,
"FileData": "R0lGODlhkAEEAYAAAAAAAAAA…"
}
]
}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
- guest-badge.webp (9.4 KB)
- Output
- guest-badge.gif (24 KB)
- Source
- A guest pass for a made-up trade show that we drew for this page, saved as a lossy WebP with a transparent background and a soft shadow. (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 GIF; 200 produced a 200 × 130 GIF of 11,078 bytes.Values: 1–20000 |
ImageHeightinteger | Height of the GIF; with ImageWidth the image is fitted inside both.Values: 1–20000 |
ScaleProportionsboolean, default true | false stretches to the exact width and height. |
ScaleIfLargerboolean, default false | true keeps a WebP smaller than the requested size at its own size. |
ScaleImageboolean, default true | false crops from the top left instead of resizing. |
TransparentColorstring | Adds one exact color to the transparent pixels. #FFFFFF gives 45,367 transparent pixels instead of 2,599, but the card is not fully see-through: the lossy badge's 10,576 near-white pixels stay opaque as light specks around the text and edges.Values: Color name, #RRGGBB or R,G,B |
ColorSpaceenum, default default | gray or monochrome before the palette is built; cmyk returns 400.Values: default, rgb, gray, monochrome |
ImageResolutioninteger | Accepted from 10 to 800 and not stored; GIF has no DPI field.Values: 10–800 |
ImageInterpolationboolean, default false | Both values accepted; resizing always uses Lanczos. |
FileNamestring | Name of the GIF; guest-badge.gif by default. |
StoreFileboolean, default false | true returns a 3-hour download URL instead of the GIF data. |
Not supported yet
Setting one of these to anything but its default returns 400.
| Option | What to do instead |
|---|---|
PersistAnimation | false returns 400 'PersistAnimation is not supported yet for WebP → GIF'. The GIF always has one frame. |
AnimationDelay | Only the documented default, 100, is accepted; there are no frames to time. |
AnimationIterations | Only 0 is accepted; the single-frame GIF has no loop setting. |
What carries over, and what doesn't
Kept
- Fully transparent pixels: all 2,599 of the sample's clear corner pixels are transparent in the GIF.
- Colors of opaque areas, closely: on the badge's opaque pixels the average error was half a level per channel, with 203 distinct colors plus the transparent entry.
- The QR code: the GIF still decodes to the pass number.
- The 400 × 260 size.
- EXIF orientation, applied before the palette is built: a 300 × 200 WebP tagged 6 came out as an upright 200 × 300 GIF.
Lost or not yet supported
- Soft transparency. The 11,188 partly transparent pixels (alpha 1 to 106) in the shadow and rounded corners became fully opaque, so a nearly invisible shadow pixel (alpha 4) turned solid dark gray and the card gained a dark rim.
- Animation: an animated WebP gives one GIF frame, the first.
- Colors beyond 256. The worst opaque pixel, on the letter inside the yellow B circle, moved by 83 levels in one channel; in the teal gradient the largest error was 36.
- EXIF, XMP and ICC metadata.
Limits
- One WebP per request, one frame per GIF.
- Requests up to 10 MB on the Free plan or 100 MB on paid plans, all files and fields together.
- WebPs above 100,000,000 pixels return 413 without being decoded.
- A conversion is stopped after 120 seconds with 504.
Where teams use it
Stills for tools that predate WebP
Some forum software, older mail clients and legacy CMS uploads take GIF but reject WebP. Convert the image and keep its fully transparent background.
A poster frame from an animated WebP
Send an animated WebP and you get its first frame as a still GIF, for a thumbnail or a preview that should not move.
Questions
Can an animated WebP become an animated GIF?
Not yet. Only the first frame is decoded and written: a three-frame test WebP gave a one-frame GIF. The animation options ConvertAPI documents for this route, such as PersistAnimation=false, return 400 instead of being ignored.
Why does my transparent WebP get a dark outline as a GIF?
GIF transparency is on or off per pixel. Pixels that are partly transparent in the WebP, like a soft shadow, are written opaque with their own color. On the sample that turned the faint shadow into a solid dark edge. Remove the shadow first, or use PNG, which keeps partial alpha.
How many colors does the GIF have?
At most 256 in the frame's palette. The badge's frame shows 203 distinct colors plus the transparent entry, with a mean error under one level on opaque pixels; smooth gradients show the steps first.
Is the GIF smaller than the WebP?
Not for the sample: the 9,596-byte badge became a 24,673-byte GIF. GIF stores each pixel as a palette index compressed with LZW, which needed more bytes here than the WebP's lossy encoding.
Convert your first WebP file in a minute.