---
seo_title: Image Compression API — Compress PNG, JPEG & WebP | EnConvert
meta_desc: Compress PNG, JPEG and WebP images via POST /v1/convert/compress-image. Lossless-first optimization, optional target file size with aspect-ratio-locked downscaling.
keywords: image compression api, compress png api, compress jpeg api, compress webp api, lossless image compression api, reduce image file size api, image optimizer rest api, compress image programmatically
---

# Image Compression API

Compress PNG, JPEG and WebP images with the `POST /v1/convert/compress-image` endpoint — the format never changes (PNG in, PNG out). Compression is lossless-first: metadata is stripped and the image is re-encoded with the strongest lossless settings for its format, so the result is never larger than the input. Pass `target_size_kb` to set a file-size budget; if lossless optimization alone can't reach it, the image is downscaled with the aspect ratio locked until it fits.

---

## Endpoint

```
POST /v1/convert/compress-image
```

**Content-Type:** `multipart/form-data`

**Accepted input:** `.png`, `.jpg`, `.jpeg`, `.webp` files

**Output format:** same as the input (`image/png`, `image/jpeg`, or `image/webp`)

---

## Authentication

Requires either a private API key or a JWT token from a public key.

```
X-API-Key: sk_live_your_private_key
```

Or:

```
Authorization: Bearer <jwt_token>
```

---

## Request Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `file` | file | Yes | -- | The `.png`, `.jpg`, `.jpeg` or `.webp` image file to compress. |
| `target_size_kb` | `integer` | No | -- | Target file size in kilobytes. Omitted: lossless optimization only. Set: the image is additionally downscaled (aspect ratio locked) until it fits the target. |
| `output_filename` | `string` | No | Input filename | Custom output filename. The input file's extension is preserved. |
| `direct_download` | `boolean` | No | `true` | When `true`, returns raw image bytes. When `false`, returns JSON metadata with a presigned download URL. |

---

## Conversion Details

**Stage 1 — lossless (always runs):**

- Metadata (EXIF, XMP, PNG text chunks) is **stripped**; the ICC color profile and the EXIF orientation flag are **preserved** so colors and rotation don't change
- **PNG:** re-encoded at maximum zlib compression, plus a palette conversion when the image has 256 or fewer unique colors and the result is provably pixel-identical
- **JPEG:** re-encoded reusing the original quantization tables with optimized progressive Huffman coding — no additional quantization loss is introduced
- **WebP:** true lossless (VP8L) re-encode at maximum effort
- The smallest of the original bytes and all candidates wins — **the output is never larger than the input**

**Stage 2 — dimension reduction (only with `target_size_kb`, only if stage 1 misses it):**

- The image is downscaled with **the aspect ratio locked** (LANCZOS resampling) and re-encoded, binary-searching the scale factor for the largest dimensions that fit the target
- Downscaled JPEG and lossy-source WebP re-encode at quality 85; PNG and lossless-source WebP stay lossless at the reduced size

<div class="alert alert-info">
<strong>Best-effort note:</strong> If the target is unreachable even at the minimum scale, the endpoint returns the smallest file it achieved instead of an error — check <code>file_size</code> (or the <code>X-File-Size</code> header) to see what was reached.
</div>

---

## Response

### Direct Download (`direct_download=true`, default)

```
HTTP 200 OK
Content-Type: image/png
Content-Disposition: inline; filename="photo_20260717_123456789.png"
```

Returns raw image bytes in the same format as the input.

### Metadata Response (`direct_download=false`)

```json
{
    "presigned_url": "https://spaces.example.com/...",
    "object_key": "env/files/{project_id}/compress-image/photo_20260717_123456789.png",
    "filename": "photo_20260717_123456789.png",
    "file_size": 45678,
    "conversion_time_seconds": 0.5
}
```

---

## Code Examples

### Python

```python
import requests

with open("photo.png", "rb") as f:
    response = requests.post(
        "https://api.enconvert.com/v1/convert/compress-image",
        headers={"X-API-Key": "sk_live_your_private_key"},
        files={"file": ("photo.png", f)},
        data={"target_size_kb": 200}
    )

with open("photo_compressed.png", "wb") as out:
    out.write(response.content)
```

### Node.js

```javascript
const form = new FormData();
form.append("file", fs.createReadStream("photo.png"));
form.append("target_size_kb", "200");

const response = await fetch("https://api.enconvert.com/v1/convert/compress-image", {
    method: "POST",
    headers: { "X-API-Key": "sk_live_your_private_key" },
    body: form
});

fs.writeFileSync("photo_compressed.png", Buffer.from(await response.arrayBuffer()));
```

### PHP

```php
$ch = curl_init("https://api.enconvert.com/v1/convert/compress-image");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["X-API-Key: sk_live_your_private_key"],
    CURLOPT_POSTFIELDS => [
        "file" => new CURLFile("photo.png"),
        "target_size_kb" => 200
    ]
]);
$output = curl_exec($ch);
curl_close($ch);
file_put_contents("photo_compressed.png", $output);
```

### Go

```go
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, _ := writer.CreateFormFile("file", "photo.png")
file, _ := os.Open("photo.png")
io.Copy(part, file)
writer.WriteField("target_size_kb", "200")
writer.Close()

req, _ := http.NewRequest("POST", "https://api.enconvert.com/v1/convert/compress-image", body)
req.Header.Set("Content-Type", writer.FormDataContentType())
req.Header.Set("X-API-Key", "sk_live_your_private_key")
resp, _ := http.DefaultClient.Do(req)
```

---

## Error Responses

| Status | Condition |
|--------|-----------|
| `400 Bad Request` | File is not a `.png`, `.jpg`, `.jpeg` or `.webp` file |
| `400 Bad Request` | File content does not match its extension (the endpoint never changes formats) |
| `400 Bad Request` | Animated image (APNG / animated WebP) — not supported |
| `400 Bad Request` | `target_size_kb` is zero or negative |
| `422 Unprocessable Entity` | `target_size_kb` is present but not a whole number |
| `400 Bad Request` | Image conversion failed (corrupt or unsupported file) |
| `401 Unauthorized` | Missing or invalid API key / JWT token |
| `402 Payment Required` | Monthly conversion limit reached |
| `402 Payment Required` | Storage limit reached |
| `413 Payload Too Large` | File exceeds plan's maximum file size |
| `400 Bad Request` | Image canvas exceeds 40,000,000 pixels (decompression-bomb guard) |

---

## Limits

| Limit | Value |
|-------|-------|
| Max file size | Plan-dependent (Free: 5 MB) |
| Max image dimensions | 40,000,000 pixels (e.g. 8000x5000) |
| Formats | PNG, JPEG, WebP (static only — no animations) |
| Target size | Best effort — unreachable targets return the smallest achievable file |
| Monthly conversions | Plan-dependent |

---

## Frequently asked questions

### How do I compress an image without losing quality?

Send the image to `/v1/convert/compress-image` without `target_size_kb`. The endpoint strips metadata and re-encodes with the strongest lossless settings for the format — PNG and lossless WebP stay pixel-identical, and JPEG keeps its original quantization tables so no further quantization loss is introduced. The result is never larger than your input.

### How do I compress an image to a specific file size?

Pass `target_size_kb` with your budget in kilobytes (for example `200` for 200 KB). Lossless optimization runs first; if the file is still over budget, the image is downscaled with the aspect ratio locked until it fits. If the target is unreachable even at the minimum scale, you get the smallest achievable file rather than an error.

### Does compression change the image format?

No, never. PNG stays PNG, JPEG stays JPEG, WebP stays WebP — the endpoint rejects files whose content doesn't match their extension instead of silently converting them.

### Is my image metadata removed?

EXIF, XMP and PNG text chunks are stripped, which often saves space by itself. Two things are deliberately kept: the ICC color profile (so colors don't shift in color-managed viewers) and the EXIF orientation flag (so rotated photos still display correctly).

### Can I compress animated images?

No. APNG and animated WebP files are rejected with a `400` error rather than being silently flattened to a single frame.
