All endpoints live under /api. Image upload endpoints accept multipart/form-data. Errors come back as JSON { error: { code, message, details? } }.
Liveness probe. Always returns 200 if the process is up.
Response 200
{
"ok": true,
"version": "1.0.0",
"env": "production",
"timestamp": "2026-04-27T18:21:00.000Z"
}Returns the supported input and output formats.
Response 200
{
"input": ["jpeg","jpg","png","webp","avif","svg","heic","heif","tiff","gif","bmp"],
"output": ["jpeg","png","webp","avif"]
}Compresses an image, optionally converting format, resizing, fitting to a target size, or applying edits.
| Field | Type | Required | Description |
|---|---|---|---|
file |
file | yes | Image to compress |
outputFormat |
string | no | One of auto, jpeg, png, webp, avif. Default auto. |
quality |
int 1–100 | no | Encoder quality. Default 80. |
maxWidth |
int | no | Resize down so width ≤ this (preserves aspect). |
maxHeight |
int | no | Resize down so height ≤ this (preserves aspect). |
stripMetadata |
bool | no | Strip EXIF/IPTC/XMP. Default true. |
targetMB |
float | no | Target output size in MB. When set, Feather binary-searches quality + resize until output ≤ target. |
mode |
string | no | final (default) or preview. |
progressive |
bool | no | Progressive JPEG. |
effort |
int 0–9 | no | WebP/AVIF effort knob. |
cropX, cropY, cropWidth, cropHeight |
int | no | Crop rectangle on the oriented source. |
rotate |
float | no | Degrees, ±360. |
flip, flop |
bool | no | Vertical / horizontal flip. |
brightness |
float 0.1–3 | no | Brightness multiplier. |
saturation |
float 0–3 | no | Saturation multiplier. |
contrast |
float 0.1–3 | no | Contrast multiplier. |
grayscale |
bool | no | Convert to grayscale. |
blur |
float 0–10 | no | Gaussian blur sigma. |
sharpen |
bool | no | Apply default sharpen. |
The body is the encoded image. Useful information rides on response headers:
| Header | Description |
|---|---|
Content-Type |
image/<format> |
Content-Disposition |
attachment; filename="<name>-compressed.<ext>" |
X-Original-Bytes |
Input size in bytes |
X-Output-Bytes |
Output size in bytes |
X-Output-Quality |
Final quality used |
X-Output-Width / X-Output-Height |
Output dimensions |
X-Compression-Ratio |
original / output (e.g. 3.42) |
X-Processing-Time-Ms |
End-to-end processing time |
X-Target-Met |
true if targetMB was satisfied |
X-Resized |
true if image was resized |
X-Iterations |
Iterations used by target-fit search |
X-RateLimit-Remaining |
Requests left in the current minute |
| Status | Code | Reason |
|---|---|---|
| 400 | INVALID_INPUT |
Missing file or invalid options |
| 413 | FILE_TOO_LARGE |
Upload exceeds MAX_UPLOAD_MB |
| 415 | UNSUPPORTED_FORMAT |
File extension/MIME not supported |
| 422 | INVALID_IMAGE |
Bytes are not a valid image |
| 422 | TOO_MANY_PIXELS |
Decoded dimensions exceed MAX_PIXELS |
| 429 | RATE_LIMIT_EXCEEDED |
Per-IP rate limit hit |
| 504 | TIMEOUT |
Processing exceeded REQUEST_TIMEOUT_MS |
| 500 | PROCESSING_FAILED |
Sharp/encoder error |
| 500 | INTERNAL_ERROR |
Unexpected server error |
Basic:
curl -X POST http://localhost:3000/api/compress \
-F "file=@photo.jpg" \
-F "outputFormat=webp" \
-F "quality=80" \
--output out.webpTarget a 500 KB cap:
curl -X POST http://localhost:3000/api/compress \
-F "file=@photo.jpg" \
-F "targetMB=0.5" \
--output out.webpResize + grayscale + AVIF:
curl -X POST http://localhost:3000/api/compress \
-F "file=@photo.jpg" \
-F "outputFormat=avif" \
-F "maxWidth=1600" \
-F "grayscale=true" \
--output out.avifPure format conversion. Same parameters as /api/compress except:
outputFormatis required and may not beauto.targetMBis ignored.
curl -X POST http://localhost:3000/api/convert \
-F "file=@photo.heic" \
-F "outputFormat=jpeg" \
-F "quality=85" \
--output out.jpgInternal endpoint used by the in-browser background-removal worker. Proxies and caches the MODNet ONNX model from a configurable upstream URL.
| Param | Values | Default | Description |
|---|---|---|---|
variant |
full | quantized |
full |
Which model to fetch. |
- Returns from in-process memory cache if warm.
- Otherwise reads from
MODEL_CACHE_DIR(if set) and warms memory. - Otherwise fetches from
MODEL_FULL_URL/MODEL_QUANTIZED_URL, persists to disk, returns.
If HUGGINGFACE_TOKEN is set, the upstream fetch sends Authorization: Bearer <token>.
Cache-Control: public, max-age=31536000, immutable. The browser worker streams this into ONNX Runtime Web.
| Status | Code | Reason |
|---|---|---|
| 502 | MODEL_FETCH_FAILED |
Upstream unreachable, returned non-2xx, or timed out (MODEL_FETCH_TIMEOUT_MS) |
Every /api/* endpoint that does work returns:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
X-RateLimit-Reset: 1714235520
When exceeded, the response is 429 Too Many Requests with Retry-After.
Configure with RATE_LIMIT_PER_MINUTE (default 60, set to 0 to disable).
Set CORS_ORIGINS to a comma-separated allowlist (or * to allow any). Preflight OPTIONS is handled by Next.js.