# Liberas Image in five minutes

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](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](/store/image) 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**,

```sh
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](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.

```sh
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.png
```

```js
const 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:

```json
{ "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](/specs/image-resize), [image-crop](/specs/image-crop), [image-transform](/specs/image-transform), [image-grayscale](/specs/image-grayscale), [image-decode-png](/specs/image-decode-png), [image-decode-jpeg](/specs/image-decode-jpeg), [image-encode-png](/specs/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; `400`s 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](contributing.md)). Missing a filter, a format, a color space? Propose it as a spec; once approved it is yours.
