BMP to SVG API
POST a bitmap to /v2/convert/bmp/to/svg and get an SVG drawn with filled paths traced from its pixels. Settings such as CurveMode, FilterSpeckle and Preset control how closely the paths follow the bitmap.
- Endpoint
- POST /v2/
convert/ bmp/ to/ svg - Accepts
- bmp
- Sample timing
- 14 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/bmp/to/svg \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F File=@shelf-label.bmp \
--form-string CurveMode=polygon \
-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/bmp/to/svg",
headers={"Authorization": f"Bearer {key}"},
files=[("File", open("shelf-label.bmp", "rb"))],
data={
"CurveMode": "polygon",
},
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 = "shelf-label.bmp";
form.append("File", new Blob([await readFile(file)]), file);
form.append("CurveMode", "polygon");
const url = "https://api.pdfconversionapi.com/v2/convert/bmp/to/svg";
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.014,
"Files": [
{
"FileName": "shelf-label.svg",
"FileExt": "svg",
"FileSize": 19441,
"FileData": "PD94bWwgdmVyc2lvbj0iMS4w…"
}
]
}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
- shelf-label.bmp (293 KB)
- Output
- shelf-label.svg (19 KB)
- Source
- A 24-bit warehouse bin label with a QR code and a colored header, 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 |
|---|---|
CurveModeenum, default spline | How outlines are drawn. The sample uses polygon: straight segments, 19,441 bytes, against 44,039 with the default curves and 25,498 with pixel, which follows the pixel grid.Values: spline, polygon, pixel |
FilterSpeckleinteger, default 4 | Patches of this value squared pixels or fewer are merged into the color around them. The label's QR modules are 5 × 5 pixels, so 5 already broke the code (112 paths); at 20 only 16 paths and 11,321 bytes were left.Values: 0–128 |
Presetenum, default none | Starting settings that your other options override. bw (98 paths) and photo (36 paths) both left the label's QR code unreadable; poster was close to the default.Values: none, bw, poster, photo |
ColorModeenum, default color | bw traces in black and white only, 32,647 bytes for the label.Values: color, bw |
ColorPrecisioninteger, default 6 | Bits per channel kept when grouping colors; 3 merged a few anti-aliased shades (170 paths instead of 173).Values: 1–8 |
GradientStepinteger, default 16 | Color step between layers. We use 16 when it is not sent; ConvertAPI documents 10. GradientStep=10 gave 44,849 bytes, 40 gave 40,194.Values: 0–128 |
Layeringenum, default stacked | cutout produces shapes that do not overlap: 60 paths and 32,568 bytes.Values: stacked, cutout |
CornerThresholdinteger, default 60 | Turns sharper than this many degrees stay corners. Our default is 60; ConvertAPI documents 55, which gave 43,789 bytes.Values: 0–180 |
SegmentLengthnumber, default 4 | Segment length for curve fitting; 8 gave 42,547 bytes. Below 3.5 is a 400.Values: 3.5–10 |
SpliceThresholdinteger, default 45 | Angle at which splines are split. Omitted, it is 45; ConvertAPI documents 35 (45,304 bytes).Values: 0–180 |
ImageWidthinteger | Resamples the bitmap before tracing. With the default curves, 200 px gave a larger SVG, 66,788 bytes against 44,039, though with fewer paths (137 against 173): smoothing adds shades (103 fill colors against 76) and each path is about twice as long. With the sample's CurveMode=polygon it was 23,324 bytes.Values: 1–20000 |
ImageHeightinteger | Resamples to this height. ScaleImage=false with both sizes traces only a top-left crop (150 × 100 gave 36 paths).Values: 1–20000 |
FileNamestring | SVG name without extension. |
StoreFileboolean, default false | Return a 3-hour link to the SVG. |
What carries over, and what doesn't
Kept
- The QR code at default settings and in the sample: a render of the SVG decoded to BIN A-14-03 | SKU HX-2207 | QTY 48
- Large flat areas, within a few levels: the teal header (31, 111, 139) came back as (32, 112, 139) and the amber box (255, 193, 37) as (251, 190, 36)
- Layout and size: the SVG is 400 × 250 and shapes stay where they were
Lost or not yet supported
- Small and anti-aliased text. In the sample, "Hex bolts" lost letters and the 14 px line "Zone A / Aisle 14" became fragments.
- Exact colors: anti-aliased edges are grouped, and the SVG uses 76 fill colors, not the BMP's 760.
- Pixel accuracy: rendered back at the same size, only 14% of pixels matched the BMP exactly, although the average difference was under 5 levels.
- Compactness for 1-bit art: a 1-bit copy of the label traced into 660 paths and 250,304 bytes.
Limits
- One BMP per request.
- Tracing stops at 12,000,000 pixels with a 413 that tells you to set
ImageWidth. - Requests up to 10 MB on the Free plan or 100 MB on paid plans, all files and fields together.
- 413 for bitmaps over 100,000,000 pixels.
- 504 after 120 seconds.
Where teams use it
Logos and signage kept only as bitmaps
Artwork that exists as a BMP from an old design tool can be traced into SVG for printing at larger sizes or for editing in a vector program.
Labels and diagrams for laser or plotter output
Cutters and plotters follow paths, not pixels. A traced label or floor diagram gives them outlines to follow.
Questions
Which settings keep a QR code scannable?
For the label, the default settings and CurveMode=polygon kept it readable. Preset=bw, Preset=photo and FilterSpeckle=20 did not. Test with your own codes, since module size matters.
Is the SVG made of real paths?
Yes, only filled path elements, 173 for the sample, and no embedded bitmap.
How do your defaults compare with ConvertAPI's?
Three differ when the option is left out: GradientStep 16 against 10, CornerThreshold 60 against 55, SpliceThreshold 45 against 35. Send ConvertAPI's numbers explicitly to match its settings.
Why is my SVG bigger than expected?
Each color cluster becomes its own paths. Photos, dithering and anti-aliased text produce many; raise FilterSpeckle, lower ColorPrecision or use CurveMode=polygon to cut the size.
Convert your first BMP file in a minute.