Developers

Compression and upscaling are both async jobs: upload to get a task id, poll and optionally let the server wait until the job finishes, then download the result. Three requests — any language can do it.

Authentication

Create an API key in the dashboard and put it in the Authorization header. The key is shown in full only once — the server stores just its hash.

Authorization: Bearer lp_live_a8f3k2…

Quick start

  1. Upload an image and get a task id

    curl -X POST https://lubanpng.wizthink.cn/v1/images/compress \
      -H "Authorization: Bearer lp_live_…" \
      -F "file=@photo.png"
    
    { "code": 0, "msg": "success", "data": { "task_id": "550e8400-…" } }
  2. Poll the result, passing wait so the server holds for up to 30 seconds

    curl "https://lubanpng.wizthink.cn/v1/images/compress/550e8400-…?wait=30" \
      -H "Authorization: Bearer lp_live_…"
    
    { "code": 0, "data": { "status": "completed",
        "original_size": 2516582, "compressed_size": 933241,
        "output_format": "jpeg", "quota_units": 1,
        "compressed_url": "/v1/images/download/550e8400-….jpg" } }
  3. Download the result — valid for 24 hours

    curl -o photo.min.jpg "https://lubanpng.wizthink.cn/v1/images/download/550e8400-….jpg"

Endpoints

Method Path & purpose Auth
POST /v1/images/compress · Upload one image and queue it; counts 1 run when the output is smaller, plus 1 more when convert is used Key / session / anonymous
POST /v1/images/upscale · Upload a static PNG or JPEG and upscale it 2× or 4×; counts 1 run when it succeeds Key / session / anonymous
GET /v1/images/compress/{task_id} · Task status, with optional wait long-polling Same as above
GET /v1/images/download/{filename} · Download a result Same as above
GET /v1/me · Current identity, plan, quota for this period and reset time Same as above
POST /v1/auth/otp · Send an email code None
POST /v1/auth/verify · Exchange a code for a sign-in session None
GET /v1/me/api-keys · List keys; POST creates, DELETE revokes Session

Format conversion

Pass convert when uploading to turn a static image into png, jpeg, webp or avif. Converting to the source format is treated as a plain compression and counts 1 run; converting to a different format costs 1 extra run. Transparent images converted to JPEG must set a #RRGGBB background colour, otherwise the task fails and every run is refunded. Animations (GIF, APNG, animated WebP) can't be converted yet — they are compressed with their animation preserved. In the task status, target_format, output_format and quota_units report the target format, the actual output format and the runs counted.

curl -X POST https://lubanpng.wizthink.cn/v1/images/compress \
  -H "Authorization: Bearer lp_live_…" \
  -F "file=@cutout.png" \
  -F "convert=jpeg" \
  -F "background=#ffffff"

{ "code": 0, "data": { "status": "completed", "target_format": "jpeg",
    "output_format": "jpeg", "quota_units": 2,
    "compressed_url": "/v1/images/download/550e8400-….jpg" } }
Format Input Convert target Notes
PNG / APNG Supported Supported APNG is quantized frame by frame and keeps its animation
JPEG Supported Supported Converting a transparent image needs background
GIF Supported Not a conversion target Animations are quantized frame by frame; delays and loops are preserved
WebP Supported Supported Animated WebP is re-encoded frame by frame
AVIF Supported Supported Static images
HEIC / HEIF Not supported by the API Not supported The web app converts photos automatically when picked in Safari on iPhone; the CLI uses the system converter on macOS before uploading; direct API uploads return 1001

Upscaling

POST /v1/images/upscale takes a static PNG or JPEG and enlarges it by scale (x2 or x4); the output is always PNG. The task model matches compression: upload to get a task id, then use the same polling and download endpoints as compression.

curl -X POST https://lubanpng.wizthink.cn/v1/images/upscale \
  -H "Authorization: Bearer lp_live_…" \
  -F "file=@logo.png" \
  -F "scale=x2"

{ "code": 0, "msg": "success", "data": { "task_id": "550e8400-…" } }
curl "https://lubanpng.wizthink.cn/v1/images/compress/550e8400-…?wait=30" \
  -H "Authorization: Bearer lp_live_…"

{ "code": 0, "data": { "status": "completed", "kind": "upscale",
    "scale": "x2", "original_size": 398829, "compressed_size": 1194351,
    "output_format": "png", "quota_units": 1, "no_gain": false,
    "compressed_url": "/v1/images/download/550e8400-….png" } }
Item Limit
Input PNG / JPEG (static; animations are not supported yet)
Factor & output x2 or x4; the output is always PNG
Input size Up to 20 MB
Input dimensions Up to 2.25 MP, longest side 2048
Output size Up to 64 MB

Upscaling is single-flight on the GPU: one job at a time, with uploads queuing while it is busy (queue depth limit 10 — beyond that the upload returns 429 with Retry-After and queue_depth). If the upscaler is unreachable, the upload returns 503.

A successful upscale costs 1 run from the same pool as compression; failures, timeouts, unsupported formats and over-limit inputs are never charged. The output is always larger than the input, so the compression rule of charging only smaller output does not apply; in the task status quota_units is the amount charged and no_gain is true only for a failed upscale.

Quota & errors

Every response carries three quota headers. A run is counted only when the output really gets smaller; failures or results that don't shrink are refunded automatically (the original is kept unless you convert, and an explicit conversion still returns the converted file even when it isn't smaller, but costs nothing); format conversion costs 1 extra run. In the task status, quota_units is the reserved estimate while the task is in flight and the runs actually charged once it reaches a terminal state; no_gain is true exactly when the task was not counted.

X-Quota-Limit: 50
X-Quota-Remaining: 46
X-Quota-Reset: 2026-10-01T00:00:00Z
HTTP code Meaning
400 1001 Bad parameters — a missing file field, an invalid convert or background value, or an unsupported format such as HEIC
413 1004 The file exceeds the current plan's size limit
401 4001 Not signed in, or the key is invalid or revoked
429 4003 Quota is used up for this period; the response carries the reset time
404 3003 The task or result doesn't exist or has expired
500 2002 Compression failed; the task is marked failed and no run is charged

For AI agents

Machine-readable entry points: llms.txt summarises both capabilities, the quota and the access channels; the OpenAPI document gives the full contract.

llms.txt · machine-readable summary

OpenAPI · full contract

Channel Status Notes
REST + API key Available Compression and upscaling share the same endpoints and quota; an anonymous cookie works in the browser
MCP server Available POST /mcp — compress, upscale, quota and task tools; a small free allowance per session

CLI

It speaks the same API and draws on the same quota. Sign in once and the key is stored in your user config directory (macOS / Linux: ~/.config/lubanpng, Windows: %APPDATA%\lubanpng).

npm i -g lubanpng

Node 24+, macOS / Linux / Windows.

$ lubanpng login
  Paste your API key: lp_live_…
  Signed in as zibin@example.com · 46 runs left this month

$ lubanpng compress ./images --out ./dist --recursive
  photo_banner.jpg   2.40 MB → 0.89 MB   -63%
  logo@2x.png         312 KB →   96 KB   -69%
  sticker_wave.gif   1.10 MB → 0.71 MB   -36%
  3 images, saved 2.11 MB, 43 runs left this month

$ lubanpng compress ./hero.png --convert webp
  hero.png           1.20 MB → 0.31 MB   -74%   → hero.webp
  1 image, saved 0.89 MB, 41 runs left this month, 1 converted

$ lubanpng usage
  Free plan · used 7 / 50 this month · resets Oct 1
command Meaning
login / logout Save or clear the local key; the LUBANPNG_API_KEY environment variable works too
compress Files or directories; --out output directory, --in-place overwrite in place, --recursive recurse, --concurrency parallelism, --convert target format (png / jpeg / webp / avif), --background fill colour for transparent images converted to JPEG; on macOS, HEIC is converted to JPEG by the system before upload
usage Plan, usage for this period and reset time

FAQ

What's the free quota?

5 runs per day anonymously, or 50 per month with a free account — one shared quota across the web app, the API and the CLI; upscaling draws from the same pool.

When is a run charged?

A compression run is charged only when the output is genuinely smaller. Failures and results that don't get smaller are never charged. Requesting a format conversion adds 1 extra run (same-format requests don't). A successful upscale costs 1 run.

Which formats are supported? Do animations stay animated?

Compression handles PNG, JPEG, GIF, WebP and AVIF — APNG, GIF and animated WebP keep their animation. Static images convert to png / jpeg / webp / avif. Upscaling accepts static PNG and JPEG at 2× or 4×.

How do I batch-compress in scripts or CI?

Use the CLI: lubanpng compress ./images --out ./dist --recursive (also --in-place, --concurrency, --convert, --background). Or create an API key and call the REST API from any language.

How long are uploaded files kept? Do download links expire?

Files from free and anonymous runs are kept for 24 hours, then deleted automatically. Downloads use a signed link valid for 10 minutes.