How to batch rtl svg icon generation with the runner and automation scripts
- Step 1Read the schema from the public API —
GET /api/v1/tools/svg-rtl-mirror(with an API key) returns the contract:input.options: [],input.acceptsMultiple: false,execution.runnerMode: "engine",minTier: "pro",output.type: svg. Use it to confirm there are no options to send — the payload is just the SVG source. - Step 2Install and pair the @jadapps/runner — Follow the runner docs at
/docs/runnerto install and pair the runner. It listens onhttp://127.0.0.1:9789. This is the only component that ever touches your files; the JAD-hosted/runendpoint will reject content with a 400 and point you here. - Step 3Build a mirror allowlist — Maintain a JSON list of which icons to flip — the tool cannot decide this for you (no auto-detect). Derive it from your design-system metadata, e.g.
{ "arrow-back": true, "logo": false }. Your script flips only thetrueentries. - Step 4Loop and POST to the runner — For each allowlisted icon, read the
.svg, POST{ text: svgSource, options: {} }tohttp://127.0.0.1:9789/v1/tools/svg-rtl-mirror/run, and write the returned SVG to your output folder. There are no per-call options. Cap concurrency (e.g. p-limit at 4–8) to be kind to the runner. - Step 5Make it idempotent with a content hash — Hash each source SVG; skip the call if the cached hash for that icon is unchanged. Because the flip is deterministic, an unchanged source always produces the same output — so a cache hit is safe and your diffs stay clean.
- Step 6Wire it into the build — Run the script as a prebuild step (
npm run generate-rtl). In CI, cache the output folder keyed by a hash of the source icons so RTL variants regenerate only when an icon actually changes.
API surface for svg-rtl-mirror
What the hosted endpoints actually do. The transform runs on your paired runner, never on JAD infrastructure.
| Endpoint | Behaviour | Notes |
|---|---|---|
GET /api/v1/tools/svg-rtl-mirror | Returns the JSON schema | options: [], acceptsMultiple: false, runnerMode: "engine", minTier: "pro" |
POST /api/v1/tools/svg-rtl-mirror/run (hosted) | 400 Runner required | Never accepts uploads; returns pairing instructions + runner endpoint |
POST http://127.0.0.1:9789/v1/tools/svg-rtl-mirror/run (runner) | Executes the flip locally | Send { text, options: {} }; one icon per call |
Payload and limits
The contract is minimal because the tool has no options. Limits are the SVG-family per-file caps; batch is orchestrated by your script.
| Field | Value | Source |
|---|---|---|
| Request body | { text: "<svg…>", options: {} } | No options exist for this tool |
| Files per call | 1 | acceptsMultiple: false |
| Per-file size (Pro) | 50 MB | SVG family Pro fileBytes (free is 5 MB) |
| Tier required | Pro | minTier: "pro" |
| Batch of N icons | N sequential/concurrent calls | Orchestrated by your script, not a batch endpoint |
Cookbook
A real runner-based pipeline. Note there is no upload to JAD and no auto-classification — your allowlist drives everything.
Confirm the contract before coding
Fetch the schema so your script targets the real shape (no options, single file, engine mode).
$ curl -H "Authorization: Bearer $JAD_KEY" \
https://jadapps.com/api/v1/tools/svg-rtl-mirror
{
"input": { "isGenerative": false,
"acceptsMultiple": false,
"options": [] },
"output": { "type": "svg" },
"execution": { "runnerMode": "engine" },
"minTier": "pro"
}The hosted /run endpoint refuses uploads
Proof that files never go to JAD's servers — the hosted run endpoint returns a 400 and hands you the local runner address.
$ curl -X POST -H "Authorization: Bearer $JAD_KEY" \
-d '{"text":"<svg…>"}' \
https://jadapps.com/api/v1/tools/svg-rtl-mirror/run
HTTP/1.1 400 Bad Request
{
"error": "Runner required",
"runnerEndpoint": "http://127.0.0.1:9789/v1/tools/svg-rtl-mirror/run",
"installRunner": "https://jadapps.com/docs/runner"
}Idempotent batch script (Node)
Reads an allowlist, flips only the marked icons via the local runner, caches by content hash, and writes a manifest.
import { readFile, writeFile, mkdir } from "node:fs/promises";
import { createHash } from "node:crypto";
import pLimit from "p-limit";
const RUNNER = "http://127.0.0.1:9789/v1/tools/svg-rtl-mirror/run";
const allow = JSON.parse(await readFile("mirror.json", "utf8")); // { name: true|false }
const cache = JSON.parse(await readFile(".rtlcache.json", "utf8").catch(() => "{}"));
const limit = pLimit(6);
await mkdir("icons-rtl", { recursive: true });
const jobs = Object.entries(allow)
.filter(([, mirror]) => mirror === true)
.map(([name]) => limit(async () => {
const src = await readFile(`icons/${name}.svg`, "utf8");
const hash = createHash("sha1").update(src).digest("hex");
if (cache[name] === hash) return { name, skipped: true }; // idempotent
const res = await fetch(RUNNER, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ text: src, options: {} }),
});
const out = await res.text();
await writeFile(`icons-rtl/${name}.svg`, out);
cache[name] = hash;
return { name, skipped: false };
}));
const results = await Promise.all(jobs);
await writeFile(".rtlcache.json", JSON.stringify(cache, null, 2));
console.log(`flipped ${results.filter(r => !r.skipped).length}, ` +
`skipped ${results.filter(r => r.skipped).length}`);Sprite sheets need a split-flip-rebuild pass
There is no sprite-aware flip. Decompose, flip the directional symbols, rebuild — a three-step pipeline.
icons.svg (sprite with many <symbol>)
1. split each <symbol> into a standalone <svg>
2. for symbols in the mirror allowlist:
POST to the runner → flipped SVG
3. rebuild → /svg-tools/svg-sprite-builder → icons-rtl.svg
Reference #icon-name from whichever sprite matches the page dir.Skip text-bearing icons (or outline first)
The flipper reverses live text. Either keep those icons LTR or outline them before flipping.
for (const name of allowlist) {
const src = await readFile(`icons/${name}.svg`, "utf8");
if (src.includes("<text")) {
// outline via svg-font-to-path first, or move to 'keep' bucket
console.warn(`${name}: has <text>; flipping would reverse it`);
continue;
}
// …POST to runner…
}Edge cases and what actually happens
POSTing files to the hosted /run endpoint
400 Runner requiredThe JAD-hosted run endpoint never accepts uploads — it returns 400 Runner required with the local runner address. This is the privacy design, not a bug. Send the payload to your paired runner at 127.0.0.1:9789 instead.
Expecting a multi-file batch endpoint
Not supportedThe schema reports acceptsMultiple: false. There is no icons[] / flags[] batch request. Batch is orchestrated by your script issuing one call per icon (concurrency capped client-side). Don't build against a non-existent multipart batch shape.
Expecting an auto-detect / confidence score
Not supportedThe tool does not analyse geometry or return a 'should this mirror' score. There is no auto-detect feature. You must supply the mirror/keep decision via your own allowlist; the tool flips exactly what you send it.
Sending options in the payload
IgnoredThe schema's options array is empty — there are no parameters. Any options object you send is effectively a no-op; the flip is fully determined by the viewBox width. Keep the payload to { text }.
Icon missing a viewBox
Fallback W=24Engine and browser both default the flip width to 24 when there is no viewBox or the width is 0. On a non-24 grid the output is off-frame. Normalise viewBoxes in a pre-step (e.g. svg-viewbox-fixer) before the batch run.
Live <text> in a batched icon
Renders reversedFlipping reverses live text into unreadable glyphs. Detect <text> in your loop and either skip those icons or route them through svg-font-to-path first. The flipper itself does nothing special for text.
Runner not paired / not running
Connection refusedIf nothing is listening on 127.0.0.1:9789, your POST fails to connect. Confirm the @jadapps/runner is installed, paired, and running. Until then the only working response from the platform is the hosted endpoint's 400 with pairing instructions.
Free-tier API key
Pro requiredminTier is pro. A free key cannot run this tool. Upgrade to Pro to use RTL Mirror via the runner. Pro also raises the per-file SVG limit to 50 MB, far above any icon.
Logos in the source folder
Excluded by allowlistNever flip the whole icons/ folder — logos and other Keep-bucket assets would be mirrored. Iterate only over allowlist entries marked true. The tool has no guard against flipping a logo.
Frequently asked questions
Does the batch API upload my icons to JAD?
No. The hosted POST /run endpoint returns 400 Runner required and never accepts files. You pair the @jadapps/runner locally and POST to http://127.0.0.1:9789/v1/tools/svg-rtl-mirror/run; the flip executes on your machine. Only the schema (GET /api/v1/tools/svg-rtl-mirror) is served from JAD.
Is there a single endpoint that flips many icons at once?
No. The schema reports acceptsMultiple: false — one icon per call. Batch is something your script does: loop over your allowlist and issue concurrent calls to the runner (cap with p-limit). There is no icons[]/flags[] multipart batch.
Does the API auto-detect which icons to mirror?
No. There is no geometry analysis or confidence score. The decision is yours — drive the run from an allowlist derived from your design-system metadata. The tool flips precisely what you send.
What do I put in the request body?
Just the SVG source: { text: "<svg…>", options: {} }. The tool has zero options (the schema's options array is empty), so there's nothing else to configure. The runner returns the mirrored SVG.
Why is execution 'engine' mode and does it matter?
RTL Mirror is pure text manipulation, so the runner imports the SVG engine and runs it directly — no headless browser. That makes per-icon calls fast and removes a Chromium dependency, which is ideal for a build step flipping hundreds of files.
How do I make the build idempotent?
Hash each source SVG and skip the call when the hash is unchanged. Because the flip is deterministic (path data untouched), an unchanged source always yields the same output, so a cache hit is safe and keeps your committed RTL files stable.
How fast is a 500-icon run?
It depends on your machine and concurrency, not on a network round-trip to JAD — everything runs locally on the runner in engine mode. With a sensible concurrency cap a few hundred small icons complete quickly; the idempotent cache means subsequent runs only touch changed files.
Can it process a sprite sheet directly?
No. Split the sheet into standalone icons, flip the directional ones through the runner, then rebuild with svg-sprite-builder. It's a three-step pipeline; the flipper operates on one whole SVG at a time.
What are the size and tier limits?
RTL Mirror is minTier: pro. The SVG family per-file limit is 50 MB on Pro (5 MB free) — irrelevant for icons. Batch size is whatever your script orchestrates, since there's no server-side batch concept for this tool.
What happens to icons that contain text?
Flipping reverses live <text>. Detect <text in your loop and either skip the icon or outline it first with svg-font-to-path. The flipper does not touch text-anchor or convert text automatically.
How do I avoid accidentally mirroring logos?
Drive the run from an explicit allowlist and iterate only over entries marked for mirroring — never feed the whole icon folder. The tool has no logo or non-directional guard; the allowlist is your safety net.
Does the runner change the output versus the browser tool?
No. RTL Mirror is server-safe, so the runner executes the same engine-mode flip the browser uses — both produce the identical scale(-1,1) translate(-W,0) wrapper with path data untouched. That's why you can develop against the browser tool and ship the runner batch with confidence the bytes match.
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.