← Pic Lite / API
Tokens

Pic Lite API

Read this first: there is no compression API. Pic Lite does all of its image work in the browser — decoding, resizing, quantising and re-encoding all happen in the tab, and no image is ever sent anywhere. There is no endpoint you can post a JPEG to, and adding one would break the only promise the app makes.

What is reachable over HTTP is the account side: the token that identifies you, your saved settings presets, and the record of past batches. That is what this page documents. If you want to compress images from a script, the honest answer is that this app is the wrong tool — use mozjpeg, oxipng, cwebp or gifsicle locally, which is what those tools are for.

Base URL and envelope

Base URL: https://api.skillsafe.ai/v1/app-api. Every request carries Authorization: Bearer <token> and X-App-Slug: pic-lite.

Every response is one of two shapes:

{ "ok": true,  "data":  { ... } }
{ "ok": false, "error": { "code": "...", "message": "..." } }

Error codes

CodeHTTPWhat it means here
UNAUTHORIZED401Missing, malformed or expired token. Mint a new one at /tokens.html.
FORBIDDEN403The token is valid but not for this app, or you asked for another owner's records.
VALIDATION_ERROR400A malformed body. The most common causes are a bare value in where instead of an operator object, and omitting slug from the /guest body.
NOT_FOUND404Unknown collection or record id.
RATE_LIMITED429120 requests per minute per IP on the data endpoints. Back off; do not tight-loop.
PAYLOAD_TOO_LARGE413A record document over the 64 KB cap. The app trims per-file rows out of a batch record before this happens.

A tiny client

Everything below is this, with a different path.

Steps

1. Get a token

Every call needs a bearer token scoped to this app. The easiest place to get one is this app's own token page — it reads the token this browser already holds, shows whether it is a personal or a guest token, and gives you a Copy shell export button. Nothing below needs the developer console.

Two kinds exist. A guest token is minted automatically by the app and is enough for /me and /storage. A personal token, from signing in, is what lets you read and write the saved presets and batch history that belong to your account. Both are passed the same way.

The samples read the token from an environment variable named SKILLSAFE_TOKEN rather than embedding it. Do not commit a token; it carries your account's authority for this app.

export SKILLSAFE_TOKEN="aut_..."

2. Mint a guest token

If you have no token at all, mint a guest one. The slug goes in the request body as well as the X-App-Slug header — a bare {} is rejected with 400 slug is required.

The response is { "token", "guest_id", "expires_at" }. There is no subject_type and no credits field on this response; if you want those, call /me afterwards.

3. Check who you are

GET /me tells you whether the token is a user or a guest, and reports a credit balance. Pic Lite never spends credits — there is no model in this app and no metered call in its run path — so the balance is informational only. Compression happens in the browser.

4. Read and write your presets

Saved settings presets live in the per-user key-value store under the key presets. The value is an array of { "name", "settings", "ts" } objects, where settings is exactly the object documented in The settings object below. Writing this key from a script is how you push one house preset onto every machine you use.

Reads of a /data key are cached for roughly 90 seconds, so a read straight after a write can return the previous value. That is why batch history uses a collection instead.

5. Query your batch history

Each completed batch is written to the declared batches collection. Records hold names, byte counts and outcomes only — never image data, not even a thumbnail. The whole point of the app is that pixels do not leave the machine, and the history feature does not make an exception.

Note two things that bite. Every where entry must be an operator object: {"saved_pct": {"gte": 40}}, never {"saved_pct": 40}. And the sort key is sort, an object — order_by is silently ignored and you get created_at desc.

6. Check your storage use

GET /storage reports what this app is holding against the per-app and per-user quotas — useful if you have been writing batch records from a script.

The settings object

This is the exact shape the app stores in a preset and in a batch record, taken from compress.js. Every field is clamped on read, so an out-of-range value written by a script is corrected rather than honoured.

FieldTypeDefaultMeaning
formatstring"keep"keep, jpeg, png or webp. keep round-trips each file's own container.
qualitynumber 1–10078JPEG and WebP only. Ignored for PNG and GIF, which have no quality dial.
scalenumber 0.1–1000100Percentage. The output is never smaller than 1×1 pixel.
maxDimnumber0Cap on the long side in pixels. 0 means no cap. Applied after scale.
noUpscalebooleantrueClamps the result to the source dimensions whatever scale asked for.
shrinkOnlybooleantrueThe anti-enlargement rule. Walks the quality or palette down, and returns the original file if nothing is smaller.
stripMetabooleantrueRemoves EXIF, GPS, XMP, ICC, IPTC and comments. Lossless on a file that is kept rather than re-encoded.
pngColorsnumber 2–256256Palette size for PNG output.
pngDitherbooleantrueFloyd–Steinberg diffusion for PNG. Skipped automatically when quantisation is exact.
gifColorsnumber 2–256128Palette size per GIF frame.
gifDitherbooleantrueDithering can make a GIF larger — it breaks up the flat runs LZW depends on.
mattestring #rrggbb"#ffffff"What transparency is flattened onto when writing JPEG.
suffixstring"-min"Appended before the extension. Empty keeps the original name.
watermarkobjectoff{ on, text, position, size, opacity, rotation, shadow, color }. position is one of top-left, top-right, bottom-left, bottom-right, center, tile.

The batch record

What a batches query returns. Records nest their document under doc — read record.doc.title, never record.title.

{
  "record_id": "rec_...",
  "doc": {
    "uid": "1786600000000-a1b2c3",
    "title": "14 files",
    "file_count": 14,
    "failed_count": 0,
    "kept_count": 2,
    "before_bytes": 18442110,
    "after_bytes": 4120887,
    "saved_pct": 77.66,
    "format": "keep",
    "ran_at": "2026-08-14T09:12:44.101Z",
    "entry": {
      "settings": { "...": "the settings object above" },
      "files": [
        { "n": "harbour.jpg", "o": "harbour-min.jpg", "b": 415720, "a": 88214, "k": 0, "e": "" },
        { "n": "favicon-tile.png", "o": "favicon-tile.png", "b": 146, "a": 146, "k": 1, "e": "" }
      ]
    }
  }
}

In the per-file rows: n is the input name, o the output name, b and a the byte counts before and after, k is 1 when the original was kept because nothing re-encoded smaller, and e carries the failure reason when a file did not finish. If entry.files_trimmed is present, the row list was shortened to fit the 64 KB document cap and is not the complete batch.

Rate limits