ICO to GIF API
POST an icon to /v2/convert/ico/to/gif and get a single-frame GIF89a back. Pixels that are fully clear in the icon stay transparent through the GIF's transparent color; every other pixel, including the partly transparent shadow, becomes opaque.
- Endpoint
- POST /v2/
convert/ ico/ to/ gif - Accepts
- ico
- Sample timing
- 39 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/ico/to/gif \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F File=@paper-plane-app-icon.ico \
-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/ico/to/gif",
headers={"Authorization": f"Bearer {key}"},
files=[("File", open("paper-plane-app-icon.ico", "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 = "paper-plane-app-icon.ico";
form.append("File", new Blob([await readFile(file)]), file);
const url = "https://api.pdfconversionapi.com/v2/convert/ico/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.039,
"Files": [
{
"FileName": "paper-plane-app-icon.gif",
"FileExt": "gif",
"FileSize": 12322,
"FileData": "R0lGODlhAAEAAYAAAAAAAAAA…"
}
]
}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
- paper-plane-app-icon.ico (34 KB)
- Output
- paper-plane-app-icon.gif (12 KB)
- Source
- A four-size app icon we drew and assembled ourselves: 16, 32 and 48 px entries stored as 32-bit bitmaps with a 1-bit mask, and a 256 px entry stored as PNG. The paper plane design is fictional. (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 | GIF width in pixels. The sample icon at ImageWidth=128 is a 5,999-byte GIF.Values: 1–20000 |
ImageHeightinteger | GIF height; alone it scales the width to match, with a width it defines a fitting box.Values: 1–20000 |
ScaleProportionsboolean, default true | false fills the box exactly even if that distorts the icon. |
ScaleIfLargerboolean, default false | true refuses to enlarge: the 256 px icon stays 256 px when you ask for 640. |
ScaleImageboolean, default true | false crops a top-left piece of the requested size; 100 × 80 gave 2,423 bytes. |
ColorSpaceenum, default default | monochrome gives a two-tone icon of 1,755 bytes; gray gives 12,938 bytes. Clear pixels stay clear in both.Values: default, rgb, gray, monochrome |
TransparentColorstring | Adds one more color to the transparent set. #FFFFFF on the sample cuts out only the plane's pure white wing (3,398 pixels); the light-blue wing, the fold and the gradient tile stay opaque.Values: Color name, #RRGGBB or R,G,B |
ImageResolutioninteger | Checked for range, then unused: a GIF has no DPI field.Values: 10–800 |
ImageInterpolationboolean, default false | Accepted; scaling always runs a Lanczos filter. |
FileNamestring | Base name of the .gif; the icon's name is used otherwise. |
StoreFileboolean, default false | Return a link to the GIF, kept for 3 hours, instead of base64. |
What carries over, and what doesn't
Kept
- Fully clear pixels: all 10,690 transparent pixels of the sample are transparent in the GIF
- Colors close to the original: 201 palette colors in use, and on the icon's opaque pixels the largest error was 54 levels, the average 0.25
- The exact colors of icons with 256 colors or fewer, which get a palette built from their own colors with no reduction
- The 256 × 256 size of the largest entry
Lost or not yet supported
- Soft edges. GIF transparency is all or nothing, so the 12,009 partly transparent pixels became solid and the sample's soft drop shadow turned into a dark band around the badge.
- Color depth beyond 256: the 256 px image has 3,013 distinct RGBA values, which are reduced to one 256-entry palette.
- Other sizes in the .ico; the GIF has a single frame.
Limits
- One .ico per request.
- Requests up to 10 MB on the Free plan or 100 MB on paid plans, all files and fields together.
ImageWidthandImageHeightup to 20,000 px; at most 100,000,000 pixels after resizing.- 120 seconds per conversion, then 504.
Where teams use it
Email templates and older web software
Some email builders, forum signatures and legacy content systems accept GIF and JPEG but not PNG or ICO. GIF is the one of those two that keeps an icon's clear background.
Icons on a flat, known background
On-or-off transparency looks fine when the icon has hard edges or will sit on a background close to its shadow color. Check the result first if your icon has a soft glow or drop shadow.
Questions
Why does my icon's shadow look black in the GIF?
A soft shadow is made of dark pixels that are partly transparent. GIF can only mark a pixel fully transparent or fully opaque, so those pixels are kept opaque with their dark color. In the sample that turned 12,009 shadow and edge pixels solid. Convert to PNG or WebP if you need the soft edge.
How are the colors reduced?
If the image uses 256 colors or fewer, the palette is built from exactly those colors. Otherwise a color quantizer picks 256 palette entries. The sample has more than 256, and its GIF uses 201 of the palette's slots.
Can I make white transparent too?
Yes, with TransparentColor=#FFFFFF, but every pure white pixel goes, wherever it is. On the sample that was the 3,398 pixels of the plane's white wing; its light-blue side (2,746 pixels) and the gradient tile kept their color.
Is the GIF animated or looping?
No. The output is one frame. An .ico holds separate sizes, not animation frames, and only one of them is used.
Convert your first ICO file in a minute.