Raster → vector conversion over HTTP. Every request is stateless; nothing is stored.
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/trace | Convert an image. Body: multipart/form-data with field image (and optional options JSON), or raw image bytes with an image/* content type, or JSON {"image": "<base64>", "options": {…}}. Query: format=svg|eps|pdf|dxf|json, dpi, unit=px|mm|cm|in|pt, width, plus any option below. |
| GET | /api/v1/options | Presets, option metadata, output formats, and input — the image formats this build decodes, with their media types and extensions. |
| GET | /api/v1/usage | Today's usage and remaining quota of the calling key. |
| GET | /healthz | Liveness, queue depth, workers. |
| GET | /metrics | Prometheus metrics, behind the same API key. |
Authentication: required — this API needs an account. Sign in or create one, make an API key for Vectorize there, and send it as X-Api-Key: ak_… (or an access token as Authorization: Bearer). Every account starts with a free allowance of traces; GET /api/v1/usage says what is left, and so do the X-Usage-Plan, X-Usage-Used, X-Usage-Limit and X-Usage-Remaining headers on each answer. A used-up plan answers 402 with upgradeUrl.
Response headers: X-Vectorize-Paths, X-Vectorize-Colors, X-Vectorize-Ms, X-Vectorize-Upscale. Errors are JSON {"error": "…"} with 400 (bad option), 401, 402 (plan used up), 413 (too large), 415 (format not decodable by this build), 422 (undecodable), 429, 503 (busy).
Accepted image formats: PNG, JPEG, WebP, GIF, BMP. Anything else answers 415 naming the format it was — HEIF/HEIC in particular, which is what an iPhone camera writes by default and which the API has no decoder for. The web app converts those because a browser decodes them with the operating system's own decoder and hands over pixels; the API only has what is compiled into it. Convert to JPEG or PNG first.
Limits on this instance: uploads up to 20 MB, images up to 10 megapixels, both answered with 413. The ceiling is on the source image: when a preset's upscale would not fit under it the factor is reduced rather than the request refused, and X-Vectorize-Upscale reports what was applied.
curl -X POST 'https://vectorize-api.dudko.dev/api/v1/trace?preset=photo&maxColors=16&format=svg' \
-H 'X-Api-Key: KEY' -F [email protected] -o photo.svg
curl -X POST 'https://vectorize-api.dudko.dev/api/v1/trace?format=pdf&dpi=300' \
-H 'X-Api-Key: KEY' -H 'Content-Type: image/png' --data-binary @logo.png -o logo.pdf
curl -X POST 'https://vectorize-api.dudko.dev/api/v1/trace?format=json&preset=poster&text=trace' \
-H 'X-Api-Key: KEY' -F [email protected] -F 'options={"maxColors":8,"transparentBackground":true}'
An MCP endpoint at https://vectorize-api.dudko.dev/mcp is the front door to this API for an
assistant: api_access says how the API works, trace_options lists the presets and
options, and api_request checks the options and returns the exact request with a short-lived
credential for the signed-in account. No picture passes through it. Point an MCP client at that
address; it finds the rest — which authorization server issues the tokens, and what scope to
ask for — at
https://vectorize-api.dudko.dev/.well-known/oauth-protected-resource/mcp.
A token is accepted only if it was issued for this address and no other.
All optional; unset options fall back to the preset. Enumerations: preset bw|poster|photo, mode color|binary, layering shared|stacked|cutout, curveMode spline|polygon|pixel, strokeMode fill|centerline, text off|trace|raster; booleans transparentBackground, adaptiveThreshold, gradients, salientColors; palette is a comma-separated list of #rrggbb.
| Option | Range | Meaning |
|---|---|---|
superpixels | 0–32 px | Groups similar neighbouring pixels before colouring. Smooths photo noise and gradient banding; too large blurs small details. |
smooth | 0–10 | Edge-preserving smoothing of the source. Flattens sensor noise and JPEG blocks while keeping real edges. |
denoise | 0–5 passes | Median filter passes. Removes isolated specks and JPEG "mosquito" noise around edges. |
cleanEdges | 0–4 passes | Removes one-pixel halos between areas left by anti-aliasing. Turn off if thin lines disappear. |
gapFill | 0–2 px | Strokes each shape in its own colour to hide hairline seams some renderers show between neighbours. |
upscale | 1–4 × | Enlarges the source before tracing. Smoother curves on small, anti-aliased images; slower on big ones. Reduced automatically when the enlarged image would not fit the pixel ceiling, rather than the image being refused. |
maxColors | 0–64 | Number of colours in the palette. Fewer colours = flatter, smaller file; more = closer to the original. |
filterSpeckle | 0–128 px² | Ignores patches smaller than this many pixels (side length). Higher = cleaner but drops small details. In black & white mode, dots and punctuation that sit on a text line are kept even when they are smaller. |
colorPrecision | 1–8 bits | Colour bits kept when clustering without a palette. Lower merges similar colours. |
layerDifference | 0–128 | Minimum colour distance between layers when clustering without a palette. |
cornerThreshold | 0–180 ° | Angle above which a bend becomes a sharp corner instead of a smooth curve. |
lengthThreshold | 3.5–10 px | Minimum segment length when fitting splines. Larger = fewer, longer curves. |
spliceThreshold | 0–180 ° | Angle below which two segments are joined into one curve. |
pathPrecision | 0–4 decimals | Decimal places written for coordinates. Fewer = smaller file, slightly less exact. |
threshold | 0–255 | Luminance cut-off for black & white. Lower keeps only dark pixels; higher keeps more. |
paletteMerge | 0–20 ΔE | Merges palette colours closer than this ΔE so a 5-colour image does not get 24 near-duplicates. |
strokeWidth | 0–20 px | Fixed width for centre-line strokes; "measured" uses each stroke's own thickness. |
gradientTolerance | 1–20 | How far a run of colour bands may deviate from a straight colour ramp and still become one gradient. |
| Preset | Effective options |
|---|---|
bw | {"mode":"binary","layering":"stacked","curveMode":"spline","denoise":0,"smooth":0,"upscale":2,"maxColors":0,"cleanEdges":0,"gapFill":0,"transparentBackground":false,"superpixels":0,"adaptiveThreshold":true,"paletteMerge":6,"strokeMode":"fill","strokeWidth":0,"gradients":false,"gradientTolerance":6,"salientColors":false,"text":"off","filterSpeckle":4,"colorPrecision":6,"layerDifference":16,"cornerThreshold":60,"lengthThreshold":4,"spliceThreshold":45,"maxIterations":10,"pathPrecision":2,"threshold":128} |
poster | {"mode":"color","layering":"shared","curveMode":"spline","denoise":0,"smooth":0,"upscale":1,"maxColors":16,"cleanEdges":1,"gapFill":0.5,"transparentBackground":false,"superpixels":0,"adaptiveThreshold":false,"paletteMerge":6,"strokeMode":"fill","strokeWidth":0,"gradients":true,"gradientTolerance":6,"salientColors":true,"text":"trace","filterSpeckle":4,"colorPrecision":8,"layerDifference":16,"cornerThreshold":60,"lengthThreshold":4,"spliceThreshold":45,"maxIterations":10,"pathPrecision":2,"threshold":128} |
photo | {"mode":"color","layering":"shared","curveMode":"spline","denoise":1,"smooth":3,"upscale":1,"maxColors":24,"cleanEdges":1,"gapFill":0.5,"transparentBackground":false,"superpixels":8,"adaptiveThreshold":false,"paletteMerge":4,"strokeMode":"fill","strokeWidth":0,"gradients":true,"gradientTolerance":8,"salientColors":true,"text":"off","filterSpeckle":8,"colorPrecision":8,"layerDifference":48,"cornerThreshold":180,"lengthThreshold":4,"spliceThreshold":45,"maxIterations":10,"pathPrecision":2,"threshold":128} |