compresso.Log in

Compresso API one request, a lighter image

Send an image, get the compressed image back in the same response. The same per-pixel quality gate as the website, now from your scripts, build pipelines and backends. Free up to 100 images a day.

Quick start

  1. Sign in with your email and copy your API key from the dashboard.
  2. Run the command below with your key and any PNG, JPEG or WebP file.
  3. photo-min.png is the compressed copy. That is the whole integration.
curl
curl https://api.compresso.space/v1/compress \
  --user api:YOUR_API_KEY \
  --data-binary @photo.png \
  --output photo-min.png \
  --fail-with-body

If 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.

Authentication

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_KEY

Compressing 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.

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-body

Response headers

Content-Typeimage/png, image/jpeg or image/webp, the same as the input.
Original-SizeSize of your upload in bytes.
Compressed-SizeSize of the returned image in bytes.
Compression-CountCompressions used by your account today (UTC), including this one.
Compression-LimitYour daily limit.
Compression-ResetWhen the count resets: the next 00:00 UTC, for example 2026-09-19T00:00:00Z.
Request-IdQuote 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
curl https://api.compresso.space/v1/usage --user api:YOUR_API_KEY
Response
{
  "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 compressions100 per account per day, reset at 00:00 UTC
File sizeup to 16 MB per image
Resolutionup to 16 megapixels
Parallel requests1 in progress and 1 waiting per account
Request rate60 requests per minute per account
Per network500 compressions per day per IP address, across all keys
API keysup to 10 active keys per account
Timeoutset 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.

CodeHTTPWhat it meansRetry?
https_required400The request used plain HTTP. Use https://api.compresso.space/v1.No
input_missing400The request body was empty. Send the image bytes.No
invalid_multipart400The form upload is malformed. Let your HTTP client build it, or send raw bytes.No
invalid_image400The file is truncated or corrupted. With curl, use --data-binary, not -d.No
unauthorized401No key, a malformed or truncated key, an unknown key, or a revoked key. The message says which.No
browser_not_supported403The request came from a web page in a browser. Call the API from a server.No
not_found404No such endpoint. The API has POST /v1/compress, GET /v1/usage and GET /v1/openapi.json.No
method_not_allowed405Wrong HTTP method for this endpoint. The Allow header lists the right one.No
file_too_large413The file is larger than 16 MB.No
image_too_large413The image is larger than 16 megapixels. It is never downscaled silently.No
unsupported_format415Not a PNG, JPEG or WebP. The message names the format that was detected.No
animated_unsupported415Animated images are not supported.No
daily_limit_exceeded429Your daily limit is used up. It resets at 00:00 UTC, see Compression-Reset.No
rate_limited429Too many requests at once or per minute. Wait Retry-After seconds.Yes, after Retry-After
server_busy503The server is busy with other work. Wait Retry-After seconds.Yes, after Retry-After
daily_capacity_reached503The API's shared daily capacity is used up. It resets at 00:00 UTC.Yes, after Retry-After
api_disabled503The API is temporarily switched off for maintenance. Try again later.Later
compress_failed500An 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.

Changelog

  • API v1 released: /v1/compress, /v1/usage, 100 free compressions a day.