Liberas Image is an HTTP API: send a PNG or JPEG with the steps to apply, get a PNG back. Every step is a WebAssembly component that contributors compete on, and the best verified one answers your call. This page is for people and for agents; the full HTTP surface is in api.md.
1. Get a key
A key is free for the first 200 calls, once per account.
- In the browser: sign in with GitHub on the store page and press Start free. The key is on the page that opens, and stays reachable from the store page as Your free key.
- From a shell or an agent: with an API token from /me,
export LIBERAS_URL=https://liberas.dev LIBERAS_TOKEN=liberas_… liberas trial image # prints the key
(The liberas CLI is @pairful/liberas on GitHub Packages; contributing.md has the install line. The same call is POST /api/products/image/trial.)
A paid pack is 5,000 calls per key. Keys have no expiry.
2. Call it
One request: POST /api/run/image-pipeline with the key, the file as base64, and the steps in order.
KEY="LIBERAS1-…"
DATA=$(base64 -i photo.jpg | tr -d '\n')
curl -s https://liberas.dev/api/run/image-pipeline \
-H 'content-type: application/json' \
-d "{\"key\":\"$KEY\",\"input\":{\"data\":\"$DATA\",\"steps\":[
{\"tag\":\"resize\",\"val\":{\"width\":800,\"height\":600,\"filter\":\"bilinear\"}},
{\"tag\":\"grayscale\"}]}}" \
| jq -r .output | base64 -d > out.pngconst data = Buffer.from(await readFile("photo.jpg")).toString("base64");
const res = await fetch("https://liberas.dev/api/run/image-pipeline", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ key, input: { data, steps: [{ tag: "resize", val: { width: 800, height: 600, filter: "bilinear" } }, { tag: "grayscale" }] } }),
});
const { ok, output, error } = await res.json();
if (ok) await writeFile("out.png", Buffer.from(output, "base64"));The answer:
{ "ok": true, "output": "<PNG as base64>", "fuel": 3616028,
"deps": { "image-decode-jpeg": { "calls": 1, "fuel": 618680 }, "image-resize": { "calls": 1, "fuel": 1715488 }, … },
"component": "7aa03c7d…" }ok: false with an error string is the pipeline's own answer (a crop outside the image, a file that is not PNG or JPEG). fuel is the work the call took; deps shows which components did it, since the pipeline is assembled from the specs it imports.
3. Steps
Steps run in the order given. Each is { "tag": …, "val": … }; grayscale has no val.
| step | val | notes |
|---|---|---|
resize | { "width", "height", "filter" } | filter is "nearest" or "bilinear". Any size from 1×1 up to 16,777,216 pixels. |
crop | { "x", "y", "width", "height" } | Must lie inside the image; otherwise the call's error. |
transform | "rotate90", "rotate180", "rotate270", "flip-horizontal", "flip-vertical" | Rotations are clockwise. |
grayscale | — | BT.601 luma, alpha kept. |
Input: PNG (any color type and bit depth; palettes and transparency are honored, 16-bit is reduced to 8) or baseline and progressive JPEG (grayscale or YCbCr). Output: always an 8-bit RGBA PNG. Images are limited to 16,777,216 pixels before and after every step. Exactly what each step computes, down to the rounding, is in the spec of each component: image-resize, image-crop, image-transform, image-grayscale, image-decode-png, image-decode-jpeg, image-encode-png. You can also call any of them on its own at POST /api/run/{spec} with the same key: they take and return decoded images ({ "width", "height", "pixels": "<RGBA as base64>" }).
4. Limits and errors
| status | meaning |
|---|---|
400 bad_input | The JSON does not match the interface: a wrong step tag, a missing field, a value out of range. Nothing ran and nothing was counted. |
402 quota_exhausted | The key has made every call its pack holds. Get a paid pack on the store page. |
403 license_invalid | The key is malformed, revoked, or for another product. |
429 rate_limited | More than 300 calls in a minute on one key. Wait a moment. |
A call may run for up to about ten seconds of CPU (its fuel budget); a 1-megapixel JPEG takes about one second. Requests count toward the pack whether or not the pipeline reports an error (ok: false), since the work was done; 400s do not.
5. Make it better, get paid
Every step is a spec anyone can beat. Submit a component that passes the same cases with at least 10% less fuel and you replace the current one, with shares of every future Image sale (contributing.md). Missing a filter, a format, a color space? Propose it as a spec; once approved it is yours.