QOI to GIF API
POST a QOI to /v2/convert/qoi/to/gif and get a single-frame GIF89a. Images with more than 256 colors get a reduced palette, and transparency becomes on or off per pixel.
- Endpoint
- POST /v2/
convert/ qoi/ to/ gif - Accepts
- qoi
- Sample timing
- 73 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/qoi/to/gif \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F File=@trail-map-card.qoi \
-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/qoi/to/gif",
headers={"Authorization": f"Bearer {key}"},
files=[("File", open("trail-map-card.qoi", "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 = "trail-map-card.qoi";
form.append("File", new Blob([await readFile(file)]), file);
const url = "https://api.pdfconversionapi.com/v2/convert/qoi/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.073,
"Files": [
{
"FileName": "trail-map-card.gif",
"FileExt": "gif",
"FileSize": 20796,
"FileData": "R0lGODlhkAEsAYAAAAAAAAAA…"
}
]
}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
- trail-map-card.qoi (66 KB)
- Output
- trail-map-card.gif (20 KB)
- Source
- A 400 x 300 trail map card with a sunrise sky, rounded transparent corners and a soft drop shadow, drawn by us and saved as a 4-channel QOI. (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 |
|---|---|
ColorSpaceenum, default default | monochrome makes a two-tone GIF of 2,535 bytes from the card; gray (24,471 bytes) spends the palette on gray levels instead of colors.Values: default, rgb, gray, monochrome; cmyk returns 400 |
TransparentColorstring | An extra color to drop to transparent, on top of pixels that are already fully clear.Values: #RRGGBB, R,G,B or a basic color name |
ImageWidthinteger | Resize first, then build the palette; at 128 px wide the card is a 6,192-byte GIF.Values: 1–20000 |
ImageHeightinteger | Height for the GIF; paired with a width it sets a bounding box.Values: 1–20000 |
ScaleProportionsboolean, default true | false allows the 4:3 card to be squeezed into another shape. |
ScaleIfLargerboolean, default false | true prevents upscaling a smaller QOI. |
ScaleImageboolean, default true | false crops instead of resizing. |
FileNamestring | Name for the GIF file. |
StoreFileboolean, default false | Store the GIF for 3 hours and send back a URL. |
What carries over, and what doesn't
Kept
- Fully transparent pixels: the 2,030 clear pixels outside the card's rounded corners are transparent in the GIF too
- Size and layout, 400 × 300 in one frame
- The exact color of 55,263 of the 100,725 opaque pixels; the rest take a nearby palette color, and the whole GIF shows 218 distinct colors
Lost or not yet supported
- Partial alpha. All 17,245 partly transparent pixels become fully opaque in their own color, reduced to the palette like the rest, so the card's soft shadow turns into a solid dark border and its rounded edges lose their smoothing.
- Smooth gradients. The sunrise sky and the glow around the sun break into visible bands with only 256 colors.
- Animation and timing: one QOI gives one still frame.
Limits
- One QOI per request.
- 65,535 px per side, the most a GIF header can store; a 65,536 px wide QOI returned 413
GIF images can be at most 65,535 pixels on a side, and this one is 65,536×1, and the same file withImageWidth=100converted. - 100,000,000 pixels per image; requests up to 10 MB on the Free plan or 100 MB on paid plans.
- 120 seconds per conversion.
Where teams use it
Legacy chat, forum and email clients
Where only GIF is safe to embed, converting a QOI screenshot or illustration gets it shown, at the cost of a reduced palette.
Small two-tone or gray images
With ColorSpace=monochrome a QOI diagram or map becomes a GIF of a few kilobytes, which suits low-bandwidth or e-ink displays.
Questions
Why does the converted card have a black border?
The QOI's drop shadow is dark pixels at low alpha. GIF can only make a pixel fully transparent or fully opaque, and the encoder keeps every pixel with any opacity, so the shadow shows at full strength. Flatten it first: convert to PNM with the AlphaColor you want, which blends with no compression loss, then convert that file with /v2/convert/pnm/to/gif.
How many colors does the GIF use?
Up to 256. The card has 8,095 distinct RGBA values; the encoder chose a 256-entry palette and the image uses 236 of them. A QOI with 256 colors or fewer keeps them exactly.
Is GIF a good target for QOI screenshots?
Only when you need GIF specifically. PNG and WebP are lossless with full alpha; the card's GIF (20,796 bytes) is smaller than its 42,524-byte WebP, but it gives up colors and soft transparency to get there.
Convert your first QOI file in a minute.