API · v1

The same engine, server-side.

Turn any image into clean, editable vector art — real paths, one fill per colour region. The API runs the same WebAssembly engine as the in-browser converter, on our machine instead of yours, so the SVG you get back is the SVG the app would have produced.

Quick start
curl -X POST "https://vectortrace.app/api/v1/vectorize?format=svg" \
  -F image=@logo.png \
  -o logo.svg

One request, one file back. No key needed for the anonymous limits below.

Authentication

Anonymous requests work, with tight limits. A key lifts them and is sent as a bearer token. A key we do not recognise is refused with 401 rather than quietly downgraded to the anonymous tier — a silent downgrade turns a typo into a rate-limit error an hour later.

Authorization: Bearer $VECTORTRACE_KEY
week 8

Week 1 keys come from an allowlist in the deployment's environment. Self-service keys arrive with metered billing.

POST/api/v1/vectorize

Vectorize one raster image. Send it as a multipart file part or as base64 inside a JSON body. The response is the vector file itself, or the document as JSON when you ask for it.

PNG · JPG · JPEG · WebP · BMP · GIF

Request

PartWhereValue
imagemultipart/form-dataThe raster file.
imageapplication/json bodyThe same file, base64 encoded, with or without a data: prefix. Use one of the two.
optionsJSON body, or form fieldAny subset of the options below. Fields you leave out take the named preset's values.
formatquery stringWhat to export. The query wins over the body.
Acceptrequest headerapplication/json returns the document and its stats instead of the file.

Examples

curl
curl -X POST "https://vectortrace.app/api/v1/vectorize?format=svg" \
  -H "Authorization: Bearer $VECTORTRACE_KEY" \
  -F "image=@logo.png" \
  -F 'options={"preset":"logo","colors":8}' \
  -o logo.svg
node
import { readFile, writeFile } from 'node:fs/promises'

const form = new FormData()
form.append('image', new Blob([await readFile('logo.png')]), 'logo.png')
form.append('options', JSON.stringify({ preset: 'logo', colors: 8 }))

const response = await fetch('https://vectortrace.app/api/v1/vectorize?format=svg', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.VECTORTRACE_KEY}` },
  body: form,
})
if (!response.ok) throw new Error(await response.text())
await writeFile('logo.svg', Buffer.from(await response.arrayBuffer()))
python
import json, os, requests

with open("logo.png", "rb") as image:
    response = requests.post(
        "https://vectortrace.app/api/v1/vectorize",
        params={"format": "svg"},
        headers={"Authorization": f"Bearer {os.environ['VECTORTRACE_KEY']}"},
        files={"image": image},
        data={"options": json.dumps({"preset": "logo", "colors": 8})},
    )
response.raise_for_status()
open("logo.svg", "wb").write(response.content)

JSON body

POST /api/v1/vectorize?format=svg
Content-Type: application/json
Accept: application/json

{
  "image": "iVBORw0KGgoAAAANSUhEUgAA…",
  "options": { "preset": "logo", "colors": 8 }
}

Options

Every field is optional. A preset fills in the rest; a field you set wins over the preset. These are the same controls as the converter's inspector, and the same table the Rust engine reads.

FieldValuesDefaultNotes
presetlogo · line-art · photo · embroidery · vinyl · laserlogoNamed option sets. Same table as the app and as the Rust engine.
modecolor · binarycolorBinary traces a single ink; color clusters a palette first.
colors2 – 6416Palette size after clustering. Ignored in binary mode.
filterSpecklepixels of area4Regions smaller than this are dropped.
cornerThreshold0 – 180 degrees60Turns sharper than this stay hard corners rather than curves.
pathPrecision0 – 42Decimals written into the path data.
curveFittingpixel · polygon · splinesplinePixel keeps the staircase, polygon fits lines, spline fits cubics.
hierarchicalstacked · cutoutstackedStacked paints regions over each other; cutout makes every region disjoint.
spliceThreshold0 – 180 degrees45Angle at which a fitted curve is split in two.
lengthThresholdpixels4Shortest polygon edge kept before simplification.
maxIterations1 – 10010Curve-fit refinement budget per subpath.

Responses and errors

Success

StatusContent-TypeBody
200image/svg+xmlThe exported SVG file, inline, with a server-derived filename.
200application/pdfThe exported PDF file, inline, with a server-derived filename.
200image/pngThe exported PNG file, inline, with a server-derived filename.
200application/jsonThe vector document and its stats, with Accept: application/json.
{
  "document": {
    "version": 1, "width": 512, "height": 512, "unit": "px",
    "palette": ["#101114", "#e8491d"],
    "elements": [{ "kind": "path", "fill": 0, "stroke": null, "subpaths": [ … ] }]
  },
  "stats": { "paths": 12, "nodes": 184, "colors": 2, "ms": 41, "estimatedSvgBytes": 3210 }
}

Every answer carries the rate-limit headers, refused or not. X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset

Error envelope

One envelope, always. Branch on the code, never on the prose — the code is stable and the message is not.

{ "error": { "code": "IMAGE_TOO_LARGE", "message": "…" } }
StatusCodeWhen
400DECODE_FAILEDThe bytes are a format we accept but could not be decoded.
400VALIDATION_ERRORThe body or a field did not match the contract.
401UNAUTHORIZEDAn Authorization header we could not read, or a key we do not know.
413IMAGE_TOO_LARGEThe image is over this caller class's pixel limit.
413PAYLOAD_TOO_LARGEThe body is over this caller class's byte limit.
415UNSUPPORTED_FORMATThe requested output format has no exporter in this build.
415UNSUPPORTED_INPUT_FORMATThe bytes are not one of the accepted rasters.
429RATE_LIMITEDHourly budget spent. Retry-After says when to come back.
500ENGINE_ERRORThe tracer ran and failed on this image.
500INTERNAL_ERROROur fault. Retry once, then tell us.

Rate limits

TierMax bodyMax pixelsRequestsCounted by
Anonymous2 MB1 MP20 / hourClient IP
Keyed20 MB16 MP600 / hourKey handle

An image over the pixel limit is refused, not downscaled. The converter in your browser reduces oversized artwork because the alternative is a quarter-gigabyte allocation in your own tab; here the ceiling is a budget, and returning coordinates that do not map onto the artwork you sent would be worse than an error.

OpenAPI

The reference above is generated from the same zod schemas the server validates with, so there is no hand-written spec to fall out of date. Point a client generator at it.

GET https://vectortrace.app/api/openapi.json

/api/openapi.json

llms.txt

A plain-text summary of the product, the pages and this endpoint, written for language models and agents. The full version adds the page index and the preset table.

GET https://vectortrace.app/llms.txt
GET https://vectortrace.app/llms-full.txt

CLI

week 10

The engine as a command. Same presets, same output, no server in the loop.

npx @vectortrace/cli in.png -o out.svg --preset laser

MCP

week 10

An MCP server exposing the engine to agents over stdio or streamable HTTP. Three tools:

ToolDoes
vectorize_imageTraces an image and returns a document handle plus stats.
list_presetsReturns the preset table with every option value.
export_documentSerializes a handle to one of the export formats.