Quick start
- Sign in with your email and copy your API key from the dashboard.
- Run the command below with your key and any PNG, JPEG or WebP file.
- photo-min.png is the compressed copy. That is the whole integration.
curl https://api.compresso.space/v1/compress \
--user api:YOUR_API_KEY \
--data-binary @photo.png \
--output photo-min.png \
--fail-with-bodyIf something goes wrong, curl exits with an error and photo-min.png contains a short JSON message that says what happened and how to fix it.
On Windows PowerShell, type curl.exe instead of curl: plain curl there is an alias for a different command.
Node.js client
For Node projects there is an official client: no dependencies, TypeScript types included, and it retries the errors that are worth retrying. It also installs a compresso command for the terminal.
npm install compresso-apiAuthentication
Every request needs your API key. Send it with HTTP Basic auth, using any user name and the key as the password (curl --user api:YOUR_API_KEY), or in an Authorization: Bearer header. Both work everywhere.
Requests must use HTTPS. Plain HTTP is refused with https_required, never redirected. If a key was ever sent over plain HTTP, revoke it in your dashboard and create a new one.
Keep keys on the server. The API refuses requests that come from a web page in a browser (browser_not_supported), so a key can never be exposed to your visitors. You can have up to 10 active keys and revoke any of them at any time.
curl https://api.compresso.space/v1/usage --user api:YOUR_API_KEYCompressing images
POST the image to /v1/compress. Send the raw file bytes as the request body, or a multipart/form-data upload with the image in a field named file. The format is detected from the file itself, so the Content-Type of a raw upload does not matter. Chunked uploads without a Content-Length work too, and hit the same size cap.
A successful response is the compressed image itself, with status 200 and the same format you sent. Save the body as a binary file. There is no second download step.
# raw bytes
curl https://api.compresso.space/v1/compress \
--user api:YOUR_API_KEY \
--data-binary @photo.png \
--output photo-min.png \
--fail-with-body
# or as a form upload (field "file")
curl https://api.compresso.space/v1/compress \
--user api:YOUR_API_KEY \
--form file=@photo.png \
--output photo-min.png \
--fail-with-bodyResponse headers
| Content-Type | image/png, image/jpeg or image/webp, the same as the input. |
|---|---|
| Original-Size | Size of your upload in bytes. |
| Compressed-Size | Size of the returned image in bytes. |
| Compression-Count | Compressions used by your account today (UTC), including this one. |
| Compression-Limit | Your daily limit. |
| Compression-Reset | When the count resets: the next 00:00 UTC, for example 2026-09-19T00:00:00Z. |
| Request-Id | Quote it when you contact us about a specific request. |
Checking your usage
GET /v1/usage returns your account's usage without compressing anything. It is a handy way to check that a key works. It does not count towards the limit.
curl https://api.compresso.space/v1/usage --user api:YOUR_API_KEY{
"ok": true,
"plan": "free",
"compression_count": 3,
"compression_limit": 100,
"compression_reset": "2026-09-19T00:00:00Z",
"key": "csk_…a1b2"
}Compressing a folder
Send images one at a time: each account compresses one image at a time with one more waiting, so parallel uploads only get rate_limited. When the API answers 429 or 503 with a Retry-After header, wait that many seconds and try again. Without Retry-After, do not retry.
// Compress every PNG/JPEG/WebP in ./images into ./images-min, one at a time.
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises"
import path from "node:path"
const KEY = process.env.COMPRESSO_API_KEY
const IN = "images"
const OUT = "images-min"
await mkdir(OUT, { recursive: true })
for (const name of await readdir(IN)) {
if (!/\.(png|jpe?g|webp)$/i.test(name)) continue
const body = await readFile(path.join(IN, name))
for (;;) {
const res = await fetch("https://api.compresso.space/v1/compress", {
method: "POST",
headers: { Authorization: `Bearer ${KEY}` },
body,
})
if (res.ok) {
await writeFile(path.join(OUT, name), Buffer.from(await res.arrayBuffer()))
console.log(name, res.headers.get("Original-Size"), "->", res.headers.get("Compressed-Size"))
break
}
const err = await res.json()
// Retry only when the API says so, and only for short waits.
const wait = Number(res.headers.get("Retry-After"))
if (wait > 0 && wait <= 60) {
await new Promise((r) => setTimeout(r, wait * 1000))
continue
}
console.error(name, err.error, err.message)
if (res.status === 429 || res.status === 503) process.exit(1) // limit or long outage
break
}
}What happens to your image
- PNG, JPEG and WebP are compressed with the same engine and the same per-pixel quality gate as compresso.space. The result stays visually identical to the original.
- The format and the pixel dimensions never change. Nothing is resized or converted.
- Compressed images carry no metadata (EXIF, GPS, camera data). Photos are rotated according to their EXIF orientation first, so they stay upright.
- If an image cannot get smaller without a visible difference, you get the original file back unchanged, metadata included, with status 200. It still counts as a compression.
- Images are processed in memory and never written to disk or stored. The image itself is never logged: we keep only sizes and the format for statistics.
Common mistakes
- curl -d mangles binary files. Always use --data-binary @file or --form file=@file.
- Do not send base64, a data: URL or JSON with an image link. Send the file bytes.
- axios needs responseType: "arraybuffer", otherwise the image is decoded as text and corrupted.
- In Python requests, write res.content (bytes), never res.text.
- Call the API from your server or build scripts, not from front-end JavaScript: browsers are refused on purpose.
Limits
| Free compressions | 100 per account per day, reset at 00:00 UTC |
|---|---|
| File size | up to 16 MB (16,777,216 bytes) per image; a form upload may add up to 1 MB of overhead |
| Resolution | up to 16 megapixels |
| Parallel requests | 1 in progress and 1 waiting per account |
| Request rate | 60 requests per minute per account |
| Per network | 500 compressions per day per IP address, across all keys |
| API keys | up to 10 active keys per account |
| Timeout | set your client timeout to at least 120 seconds |
Errors
Every error is JSON: {"ok": false, "error": "code", "message": "...", "docs": "...", "request_id": "..."}. The message explains what happened and what to do, so print it.
Retry rule: if a 429 or 503 carries a Retry-After header, wait that many seconds and retry. If there is no Retry-After, do not retry automatically.
| Code | HTTP | What it means | Retry? |
|---|---|---|---|
| https_required | 400 | The request used plain HTTP. Use https://api.compresso.space/v1. | No |
| input_missing | 400 | The request body was empty. Send the image bytes. | No |
| invalid_multipart | 400 | The form upload is malformed. Let your HTTP client build it, or send raw bytes. | No |
| invalid_image | 400 | The file is truncated or corrupted. With curl, use --data-binary, not -d. | No |
| unauthorized | 401 | No key, a malformed or truncated key, an unknown key, or a revoked key. The message says which. | No |
| browser_not_supported | 403 | The request came from a web page in a browser. Call the API from a server. | No |
| not_found | 404 | No such endpoint. The API has POST /v1/compress, GET /v1/usage and GET /v1/openapi.json. | No |
| method_not_allowed | 405 | Wrong HTTP method for this endpoint. The Allow header lists the right one. | No |
| file_too_large | 413 | The file is larger than 16 MB (16,777,216 bytes). | No |
| image_too_large | 413 | The image is larger than 16 megapixels. It is never downscaled silently. | No |
| unsupported_format | 415 | Not a PNG, JPEG or WebP. The message names the format that was detected. | No |
| animated_unsupported | 415 | Animated images are not supported. | No |
| daily_limit_exceeded | 429 | Your daily limit is used up. It resets at 00:00 UTC, see Compression-Reset. | No |
| rate_limited | 429 | Too many requests at once or per minute. Wait Retry-After seconds. | Yes, after Retry-After |
| server_busy | 503 | The server is busy with other work. Wait Retry-After seconds. | Yes, after Retry-After |
| daily_capacity_reached | 503 | The API's shared daily capacity is used up. It resets at 00:00 UTC. | Yes, after Retry-After |
| api_disabled | 503 | The API is temporarily switched off for maintenance. Try again later. | Later |
| compress_failed | 500 | An unexpected error on our side. Retry once, then contact us with the request_id. | Once |
OpenAPI
The full machine-readable description of the API is at https://api.compresso.space/v1/openapi.json (OpenAPI 3.1, no key needed). Import it into Postman or Insomnia, or generate a client from it.
FAQ
- Is the Compresso API free?
- Yes. Every account gets 100 compressions a day for free, reset at 00:00 UTC. You sign in with your email; no card is needed.
- Do I need to download the result separately?
- No. The compressed image comes back in the same response as your upload, so one request is all it takes. The response headers tell you the original size, the new size and how many compressions you have left today.
- Which formats and sizes does it accept?
- PNG, JPEG and WebP, up to 16 MB (16,777,216 bytes) and 16 megapixels per image. You get the same format back, with the same pixel dimensions.
- Are my images stored?
- No. Images are processed in memory and never written to disk or stored. The image itself is never logged; only sizes and the format are kept for statistics.
- Do I need an SDK?
- No. It is a plain HTTPS POST that any language can make. This page has ready examples for curl, Node.js, Python and PHP, and the OpenAPI description lets you generate a client or import it into Postman.
Changelog
- API v1 released: /v1/compress, /v1/usage, 100 free compressions a day.