How to batch wave svg generation with the jad runner and api
- Step 1Mint an API key and pair the runner — Generate an API key (the API rejects unauthenticated calls). Install and pair @jadapps/runner per the runner docs — it listens on
http://127.0.0.1:9789. SVG tools run in the runner's in-process engine, so no browser session is launched. - Step 2Fetch the schema once —
GET /api/v1/tools/svg-wave-divider(with your key) returnsinput.options— the five fields with their min/max/step — andexecution.runnerMode: "engine". Build your config types from this so you never reference a field that doesn't exist. - Step 3Define your variant matrix — In your own script, list the combinations you need: e.g. an array of
{ name, waveColor, waveAmplitude, waveFrequency, waveHeight, waveFlipped }. Keep amplitude below height/2 to avoid clamped flat tops. There is no shared-shapeseed— identical numeric options already produce identical shapes (the sine is deterministic). - Step 4Dispatch each payload to the local runner — POST each option object to
http://127.0.0.1:9789/v1/tools/svg-wave-divider/run. The runner returns the generated SVG. Note: posting to the cloud/runinstead returns 400 with pairing instructions on purpose — always target the localhost runner for execution. - Step 5Write each SVG to disk with a meaningful name — Save responses as
wave-hero.svg,wave-pricing.svg, etc. (the tool's own download name is alwayswave-divider.svg, so name them in your script). Put them underpublic/for static serving. - Step 6Wire generation into the build — Run the script as a
prebuildstep. In Next.js, files inpublic/are served as-is, so reference/wave-hero.svgfrom an<img>or inline the contents. Re-running the build regenerates every variant deterministically.
The real API surface for svg-wave-divider
What each endpoint does. The cloud API never executes — it authenticates, rate-limits, and hands you the schema; the runner executes locally.
| Endpoint | Method | Returns | Notes |
|---|---|---|---|
/api/v1/tools/svg-wave-divider | GET | Tool metadata + input.options schema + execution.runnerMode | Requires API key; rate-limited; runnerMode is engine for SVG |
/api/v1/tools/svg-wave-divider/run | POST | HTTP 400 "Runner required" + pairing instructions | Upload-free by design; cloud never executes — points you to the runner |
http://127.0.0.1:9789/v1/tools/svg-wave-divider/run | POST | The generated SVG | Local @jadapps/runner; in-process engine; nothing leaves your machine |
Options you can send (and what isn't there)
The only fields the runner accepts for svg-wave-divider, with their validated ranges. Fields on the right are commonly assumed but do not exist — sending them is ignored.
| Real option | Type / range | Default | Does NOT exist |
|---|---|---|---|
waveAmplitude | number, 5–120 (engine ≥2) | 40 | seed, irregularity |
waveFrequency | number, 0.5–10 | 3 | animate, animationDuration |
waveHeight | number, 20–240 | 80 | width (viewBox width is fixed at 1440) |
waveColor | colour string | #6366f1 | layers, gradient / second colour |
waveFlipped | boolean | false | contrastRatio output field |
Cookbook
Real automation recipes against the real endpoints. Replace YOUR_KEY and target the localhost runner for execution.
Fetch the schema before building payloads
The GET endpoint tells you the exact option fields and ranges. Build your variant config against this so you never send a non-existent field.
curl -H "Authorization: Bearer YOUR_KEY" \
https://jadapps.com/api/v1/tools/svg-wave-divider
# → { ..., "input": { "isGenerative": true, "options": [
# {"name":"waveAmplitude","min":5,"max":120,"default":40}, ... ] },
# "execution": { "runnerMode": "engine" }, "minTier": "free" }Why POSTing to the cloud /run returns 400
The cloud /run is upload-free and never executes — it returns 400 with the runner endpoint. This is expected; switch your POST target to the localhost runner.
curl -X POST -H "Authorization: Bearer YOUR_KEY" \
-H 'Content-Type: application/json' \
-d '{"options":{"waveAmplitude":40,"waveColor":"#6366f1"}}' \
https://jadapps.com/api/v1/tools/svg-wave-divider/run
# HTTP 400
# { "error": "Runner required",
# "runnerEndpoint": "http://127.0.0.1:9789/v1/tools/svg-wave-divider/run" }Loop a colour matrix against the local runner
Read the schema, then iterate your section/colour matrix in Node, dispatching each payload to the paired runner and writing the SVG to public/.
const variants = [
{ name: 'hero', waveColor: '#ffffff', waveAmplitude: 40, waveFrequency: 3 },
{ name: 'pricing', waveColor: '#0f172a', waveAmplitude: 25, waveFrequency: 4 },
{ name: 'cta', waveColor: '#6366f1', waveAmplitude: 60, waveFrequency: 2, waveFlipped: true },
];
for (const v of variants) {
const r = await fetch('http://127.0.0.1:9789/v1/tools/svg-wave-divider/run', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ options: v }),
});
fs.writeFileSync(`public/wave-${v.name}.svg`, await r.text());
}Per-breakpoint amplitude, not per-breakpoint width
There is no width option — the viewBox is fixed at 1440 and stretches via preserveAspectRatio. For mobile, vary amplitude (a narrow viewport compresses the curve) rather than expecting a width parameter.
// Desktop and a punchier mobile variant of the same colour:
const desktop = { name: 'hero-lg', waveColor: '#fff', waveAmplitude: 40 };
const mobile = { name: 'hero-sm', waveColor: '#fff', waveAmplitude: 70 };
// Serve via <picture><source media="(max-width:640px)" srcset="/hero-sm.svg"></picture>Deterministic shapes without a seed
Identical numeric options always yield an identical path — the sine is deterministic, so the same amplitude/frequency/height across colour variants produces sibling shapes automatically. No seed field is needed or available.
const shape = { waveAmplitude: 40, waveFrequency: 3, waveHeight: 80 };
['#fff','#0f172a','#6366f1'].forEach((c, i) =>
dispatch({ ...shape, waveColor: c, _name: `wave-${i}` })
);
// All three share the exact same curve; only the fill differs.Edge cases and what actually happens
POST to the cloud /run endpoint
400 Runner requiredBy design the cloud /api/v1/tools/svg-wave-divider/run is upload-free and never executes — it returns HTTP 400 with runnerEndpoint pointing at http://127.0.0.1:9789/.... Switch your POST target to the local runner. This is the intended flow, not an error in your request.
Calling without a valid API key
401 / 403Both the GET schema and POST run endpoints call validateApiKey and reject missing or invalid keys with an auth error before doing anything else. Org-issued keys also carry per-tool scopes — a key not scoped for this tool is denied. Mint and scope a key first.
Sending waveAmplitude above the max
Rejected (out of range)validateSvgOptions enforces the schema min/max: waveAmplitude must be ≤120, waveFrequency ≤10, waveHeight ≤240. An out-of-range number throws a descriptive error instead of producing degenerate output. Clamp your matrix to the documented ranges.
Sending a non-numeric option value
Rejected (not a number)If a numeric field receives a value that isn't finite (e.g. "abc" or NaN), validation throws must be a number. Cast your config values to numbers before dispatching. Omitting a field is fine — the engine default applies.
Including animate / seed / width / layers in the payload
IgnoredThese fields are not in the schema, so the runner ignores them — they do not error, but they do nothing. Don't build your config types around them; the only knobs are the five documented options.
Exceeding the per-hour rate limit
429 Rate limit exceededBoth endpoints enforce a per-key hourly rate limit and return 429 with X-RateLimit-* headers and a resetAt. For large matrices, batch within the limit or pace your loop. Execution itself is local, but each schema/dispatch call still counts against the cloud rate limit where applicable.
Expecting a contrastRatio field in the response
Not returnedThe response is the SVG (engine mode), with no computed contrast metadata. To validate text-over-wave contrast, compute it yourself from waveColor against your overlay colour using a WCAG contrast formula in your own script.
Filenames collide because the tool names everything wave-divider.svg
By designThe browser download is always wave-divider.svg. In automation you control the filename when you write the response to disk — use your variant name (wave-hero.svg). Don't rely on the tool to disambiguate names for you.
Frequently asked questions
Is there a /wave/batch endpoint?
No. There is no batch endpoint and no wave-specific route. The pattern is: GET /api/v1/tools/svg-wave-divider for the schema, then loop your variant matrix in your own script and dispatch each payload to the local runner at http://127.0.0.1:9789/v1/tools/svg-wave-divider/run. The cloud /run is upload-free and returns 400 by design.
Why does POSTing to the cloud /run return 400?
On purpose. JAD's API never accepts uploads or executes transforms — /run always returns HTTP 400 with error: "Runner required" and a runnerEndpoint field. Real execution is local, via a paired @jadapps/runner. Point your POST at http://127.0.0.1:9789/... instead and you'll get the SVG back.
Does generating waves spin up a headless browser?
No. For SVG tools the schema reports execution.runnerMode: "engine", meaning the runner executes the transform in-process. Only DOM/Canvas/font-fetch tools use a headless browser. Wave generation is pure math, so it's engine mode and fast.
Which options can I actually vary in a batch?
Exactly five: waveAmplitude (5–120), waveFrequency (0.5–10), waveHeight (20–240), waveColor, and waveFlipped. Anything else — seed, irregularity, animate, animationDuration, width, layers, contrastRatio — is not in the schema and is ignored. Build your config against the GET schema to stay accurate.
How do I keep wave shapes consistent across colour variants?
Fix waveAmplitude, waveFrequency, and waveHeight and vary only waveColor. The sine is deterministic, so identical numeric options always yield an identical path — no seed field is needed because there's no randomness to seed. All your variants will be exact-shape siblings.
Can I generate animated wave SVGs in a batch?
Not from the tool — the output is always a static SVG with no animation markup. There is no animate option. If you want motion, add CSS keyframes around the SVG in your own component or stylesheet after generation, gated behind prefers-reduced-motion.
Can I request different widths per breakpoint?
No — the viewBox width is fixed at 1440 and there is no width option. Responsiveness comes from width="100%" + preserveAspectRatio="none". For a mobile-specific feel, vary waveAmplitude (a narrow viewport flattens the curve) and serve variants with <picture> source media queries.
How do I validate contrast for text over the wave?
Compute it yourself. The runner returns the SVG only — there is no contrastRatio field. Take your waveColor and your text colour and run them through a standard WCAG contrast-ratio function in your build script, flagging any pair below 4.5:1 for normal text.
Do I need a paid tier to automate this?
The tool itself is free-tier and the API/runner model is the same across tiers. You do need an API key, and org-issued keys must be scoped for the tool. Rate limits are per key per hour. Execution is local either way, so your SVGs never leave your machine.
How do I name the output files?
In your script. The tool's own download is always wave-divider.svg, so when you write the runner's response to disk, name it from your variant (e.g. public/wave-hero.svg). That gives you predictable lookups in templates.
Where do generated waves go in a Next.js project?
Write them to public/ in a prebuild step; Next serves everything in public/ statically at the root path. Reference them as /wave-hero.svg from an <img>, or read the file at build time and inline the SVG markup if you need to recolour via currentColor.
Is the same model used for the other JAD generators?
Yes — blobs, low-poly fields, and pattern tiles all use the same upload-free API plus local runner. See the parallel guides for batch blob generation, batch low-poly backgrounds, and batch pattern generation.
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.