How to automate svg path simplification across an icon library
- Step 1Read the tool schema first —
GET /api/v1/tools/svg-path-simplifierreturns the structured contract:category,outputType: svg,minTier: pro,acceptsMultiple: false,execution.runnerMode: engine, and theoptionsarray — a single{ name: "tolerance", type: "number", default: 1.0, min: 0.1, max: 5, step: 0.1 }. Build your payload from this; do not assume options that aren't there. - Step 2Do NOT POST files to the cloud /run endpoint —
POST /api/v1/tools/svg-path-simplifier/runvalidates your API key and rate limit, then returns 400 "Runner required" withrunnerEndpoint,schemaEndpoint, andinstallRunnerfields. This is intentional — the cloud never accepts uploads. Treat that 400 as the signal to dispatch to your local runner instead. - Step 3Pair the @jadapps/runner — Install and pair the runner once (see
/docs/runner). It listens onhttp://127.0.0.1:9789. For the Path Simplifier it runs in engine mode — no browser session needed — so it starts fast and is CI-friendly. - Step 4Loop your directory, one file per call — Read each
.svg, POST{ files: [...], options: { tolerance: 1.0 } }tohttp://127.0.0.1:9789/v1/tools/svg-path-simplifier/run, and write the returned SVG back out. There is no multi-file/ZIP endpoint — concurrency is up to your script. Keep a modest parallelism so you don't starve the runner. - Step 5Vary tolerance per category if needed — Run the loop once per directory with a different
tolerance: geometric polygons at0.5, rough bitmap traces at2.0. Validate each numeric value stays within0.1–5.0— the schema validator and the runner both reject out-of-range numbers with a descriptive error. - Step 6Gate with visual regression and chain a minifier — After the batch, run your visual-regression suite. Eligible polylines that over-simplified will show as diffs; bump their tolerance down and re-run just those. RDP doesn't strip bytes elsewhere, so finish with SVGO or the Precision Tuner for the full size win.
The real API surface for svg-path-simplifier
Two endpoints, one of which never accepts uploads. Values come straight from the schema and the /run route handler.
| Endpoint | Method | What it returns |
|---|---|---|
/api/v1/tools/svg-path-simplifier | GET | JSON schema: options (tolerance), minTier: pro, outputType: svg, acceptsMultiple: false, execution.runnerMode: engine |
/api/v1/tools/svg-path-simplifier/run | POST | 400 "Runner required" — pairing instructions; never accepts file content |
http://127.0.0.1:9789/v1/tools/svg-path-simplifier/run | POST | Executes locally on the paired runner; returns the simplified SVG. This is where work actually happens |
Payload contract (single file per call)
There is exactly one option. acceptsMultiple is false for this tool (only svg-sprite-builder accepts multiple inputs), so loop one file at a time.
| Field | Type / range | Notes |
|---|---|---|
files | array of one file payload | One SVG per call; no ZIP/multi-file endpoint exists for this tool |
options.tolerance | number 0.1–5.0, step 0.1 | Default 1.0 if omitted; out-of-range throws a descriptive validation error |
text (alt to files) | raw SVG string | Accepted in place of a file, same as the web textarea |
| Output | image/svg+xml | Eligible M/L paths simplified; curve/H/V paths returned unchanged |
Cookbook
Real request/response shapes. The cloud /run returns 400 by design; the local runner does the work.
Discover the schema
Fetch the contract before building any payload. This is the authoritative list of options — a single number.
$ curl -s https://jadapps.com/api/v1/tools/svg-path-simplifier
{
"slug": "svg-path-simplifier",
"category": "performance",
"outputType": "svg",
"minTier": "pro",
"acceptsMultiple": false,
"options": [
{ "name": "tolerance", "type": "number",
"default": 1.0, "min": 0.1, "max": 5, "step": 0.1 }
],
"execution": { "runnerMode": "engine" }
}The cloud /run endpoint refuses uploads
This is expected behaviour, not an error in your code. The 400 hands you the runner endpoint to use instead.
$ curl -s -X POST https://jadapps.com/api/v1/tools/svg-path-simplifier/run \
-H "Authorization: Bearer $JAD_KEY" --data @icon.svg
{
"error": "Runner required",
"reason": "JAD's API/MCP never accepts uploads ...",
"schemaEndpoint": ".../api/v1/tools/svg-path-simplifier",
"runnerEndpoint": "http://127.0.0.1:9789/v1/tools/svg-path-simplifier/run",
"installRunner": ".../docs/runner"
}Node loop over a directory via the local runner
The actual batch pattern: read each SVG, POST to the runner one file at a time, write the result. No ZIP, no cloud upload.
import { readdir, readFile, writeFile } from "node:fs/promises";
const RUNNER = "http://127.0.0.1:9789/v1/tools/svg-path-simplifier/run";
const TOL = 1.0;
for (const name of await readdir("./icons")) {
if (!name.endsWith(".svg")) continue;
const svg = await readFile(`./icons/${name}`, "utf8");
const res = await fetch(RUNNER, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ text: svg, options: { tolerance: TOL } }),
});
const out = await res.text();
await writeFile(`./icons-out/${name}`, out);
}Per-category tolerance
Run the loop once per subdirectory with a different value. Keep each value inside 0.1–5.0 or the runner rejects it.
const jobs = [
{ dir: "./icons/geometric", tolerance: 0.5 }, // crisp polygons
{ dir: "./icons/traced", tolerance: 2.0 }, // rough bitmap traces
];
for (const j of jobs) await simplifyDir(j.dir, j.tolerance);
// out-of-range example:
// tolerance: 9 → 400 'Option "tolerance" must be \u2264 5 (received 9).'npm script + visual regression gate
Wire the loop into package.json and follow it with a snapshot check so over-simplified polylines fail CI.
// package.json
{
"scripts": {
"icons:simplify": "node scripts/simplify-icons.mjs ./src/icons",
"icons:check": "npm run icons:simplify && playwright test icons.spec.ts"
}
}
// curve/H/V icons pass through untouched, so they never regress;
// only eligible polylines can differ — those are what the snapshot guards.Edge cases and what actually happens
POSTing files to the cloud /run endpoint
400 by design/api/v1/tools/svg-path-simplifier/run always returns 400 "Runner required" with pairing instructions — it never accepts upload content. If your pipeline treats that as a hard failure, you've misread the contract: the 400 body carries the runnerEndpoint you should dispatch to instead. Files only ever process on your own paired runner.
No multi-file or ZIP batch endpoint
UnsupportedacceptsMultiple is false for this tool (only svg-sprite-builder accepts multiple inputs), and there is no /optimize-paths or ZIP-returning endpoint. "Batch" is your script looping one file per runner call. Any older doc referencing a 50-files-per-call ZIP endpoint or a files-per-call rate limit is describing functionality that does not exist.
tolerance out of range
RejectedThe schema validator enforces 0.1–5.0. A value below 0.1 or above 5.0 throws Option "tolerance" must be \u2265 0.1 / \u2264 5, and a non-numeric value throws must be a number. The browser slider clamps the same range, so the web and API entry points agree. Clamp client-side before dispatching to avoid round-trips.
Every path in a file is curves
PreservedBézier/arc icon sets are skipped entirely — the runner returns the SVG unchanged with Curve paths preserved equal to the path count. In a batch this looks like "no savings" on those files, which is correct: the tool only simplifies M/L polylines. Don't add a retry loop expecting curves to eventually shrink; they won't.
H/V straight lines aren't simplified
PreservedPaths using H/V shorthand are excluded by the eligibility regex even though they're straight. If your traced output uses H/V, normalise them to explicit L commands in a pre-pass (SVGO can do this) so the simplifier can act on them.
File over the tier size cap
RejectedSVG limits are 5 MB (free), 50 MB (Pro), 2 GB (Developer). The tool is Pro-minimum, so a free key can't run it at all. In a batch, pre-filter by size or catch the per-job-limit error and route oversized assets to a Developer-tier runner.
Runner not paired / not on 127.0.0.1:9789
Connection errorIf the runner isn't running, your POST to http://127.0.0.1:9789/... fails at the network layer (connection refused), not with a JAD HTTP status. Health-check the runner before the loop and surface a clear "pair the runner" message; see /docs/runner.
Rate limit on the discovery/key calls
429Authenticated cloud calls (schema fetch, the deliberate /run 400) count against your key's hourly rate limit and return 429 with X-RateLimit-* headers when exceeded. The actual simplification runs locally on the runner and does not consume cloud quota — so fetch the schema once and cache it rather than per file.
Frequently asked questions
Is there a batch endpoint that takes a ZIP of SVGs?
No. This tool's acceptsMultiple is false and there is no ZIP-batch or /optimize-paths endpoint. Batch processing means scripting a loop of single-file calls against your local @jadapps/runner. Any reference to a 50-files-per-call ZIP endpoint is inaccurate — that functionality doesn't exist.
Why does POSTing my SVG to /run return 400?
By design. /api/v1/tools/svg-path-simplifier/run never accepts upload content — JAD's API/MCP layer doesn't receive files. The 400 "Runner required" response includes runnerEndpoint (http://127.0.0.1:9789/v1/tools/svg-path-simplifier/run), schemaEndpoint, and installRunner. Dispatch the same payload to your local runner instead; that's where execution happens.
How do I discover the available options programmatically?
GET /api/v1/tools/svg-path-simplifier returns the schema. For this tool the options array has exactly one entry: tolerance (number, min 0.1, max 5, step 0.1, default 1.0). It also reports minTier: pro, outputType: svg, acceptsMultiple: false, and execution.runnerMode: engine.
Does the runner need a headless browser for this tool?
No. The Path Simplifier is engine-mode — a pure-text RDP transform — so the runner executes it directly without launching a headless browser. That makes it fast and well-suited to CI. (Headless mode is reserved for SVG tools that emit PNG/ZIP or need DOM/Canvas, like the favicon and app-icon generators.)
Can I set different tolerances for different icon categories?
Yes — run the loop once per directory with a different tolerance: e.g. 0.5 for crisp geometric polygons, 2.0 for rough bitmap traces. Each value must stay within 0.1–5.0 or both the schema validator and the runner reject it with a descriptive error.
Will curve-based icons get simplified in the batch?
No. Paths containing C/S/Q/T/A (or H/V) are passed through unchanged. In a mixed library, polyline outlines simplify while Bézier icons return identical. That's why curve-heavy icon sets show little or no batch savings from this tool — it isn't a curve simplifier.
What rate limits apply?
Hourly limits apply to authenticated cloud calls (schema fetch and the deliberate /run 400), returned via X-RateLimit-* headers with a 429 when exceeded; the exact ceiling depends on your key. The real simplification runs on your local runner and consumes no cloud quota — so cache the schema and only the runner does per-file work.
How do I integrate this into a build pipeline?
Add an npm script that runs your Node loop (read SVG → POST to the runner → write result), then a visual-regression step. Only eligible polylines can change, so curve icons never regress. Finish with SVGO or the Precision Tuner for byte stripping that RDP doesn't do.
Does path simplification interact badly with CSS transitions?
Transitions on fill/stroke are unaffected. Morphing the d attribute requires matching node counts between keyframes — and remember this tool only changes node counts on M/L polylines, never on curves. If you morph polyline keyframes, simplify them all at the same tolerance so node counts stay aligned; see the animation guide.
Can I self-host the RDP instead of using the runner?
Yes — RDP is a small algorithm; the simplify-js npm package implements it directly, and you can replicate this tool's eligibility rule (skip any d containing C/S/Q/T/A/H/V, simplify M/L only, round to two decimals). Use the runner when you want behaviour identical to the web tool without re-implementing edge cases.
Is any file content sent to JAD's servers during a batch?
No. The cloud endpoints only exchange the schema and the runner-required 400; they never receive SVG content. Every simplification happens on your own paired runner on localhost, so the files stay on your network end to end.
How big can each file be in the batch?
SVG limits are 5 MB (free, but the tool is Pro-minimum so free can't run it), 50 MB (Pro), and 2 GB (Developer). Processing cost scales with total polyline node count, not raw bytes — a file dense with M/L points takes longer than a large file of curves the tool skips. Pre-filter oversized assets in your loop.
Privacy first
Every JAD SVG tool runs entirely in your browser using the DOM API and Canvas. Your SVG files never leave your device — verified by zero outbound network requests during processing.