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
Best-effort note: If the target is unreachable even at the minimum scale, the endpoint returns the smallest file it achieved instead of an error — check file_size (or the X-File-Size header) to see what was reached.

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)#

{
    "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#

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#

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#

$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#

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.