Guides
Compress images
Make JPEG, PNG, WebP, AVIF, HEIC and GIF files smaller, change their format, resize them and remove their metadata in one request.
Basic request
Send the image as the body of POST /v1/compress. The compressed image is the response body. This example converts a JPEG to AVIF:
curl -X POST "https://api.smolmac.com/v1/compress?image.format=avif" \
-H "Authorization: Bearer $SMOL_API_KEY" \
--data-binary @photo.jpg \
-o photo.avifimport { readFile, writeFile } from "node:fs/promises";
const response = await fetch("https://api.smolmac.com/v1/compress?image.format=avif", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.SMOL_API_KEY}` },
body: await readFile("photo.jpg"),
});
if (!response.ok) throw new Error((await response.json()).error.message);
await writeFile("photo.avif", Buffer.from(await response.arrayBuffer()));
const result = JSON.parse(response.headers.get("smol-result"));
console.log(result.output_format, result.width, result.height, result.savings_percent);import json, os, requests
with open("photo.jpg", "rb") as f:
response = requests.post(
"https://api.smolmac.com/v1/compress",
params={"image.format": "avif"},
headers={"Authorization": f"Bearer {os.environ['SMOL_API_KEY']}"},
data=f,
)
response.raise_for_status()
with open("photo.avif", "wb") as out:
out.write(response.content)
result = json.loads(response.headers["smol-result"])
print(result["output_format"], result["width"], result["height"], result["savings_percent"])With no options, the API keeps the format, uses quality 75, fits the image within 2000 by 2000 pixels and removes metadata. To reuse one set of options across requests, save it as a preset and send ?preset=. The Smol-Result response header carries the full result object as JSON:
{
"kind": "image",
"input_format": "jpg",
"output_format": "avif",
"original_size": 1614027,
"output_size": 212904,
"savings_percent": 86.8,
"kept_original": false,
"increased": false,
"width": 2000,
"height": 1333,
"pages": null,
"duration_seconds": null,
"video_codec": null,
"warnings": []
}Formats in and out
The type of the file is read from its content. A JPEG named photo.png is treated as a JPEG. The name matters only for camera RAW, where several formats share one container: send it with ?filename=IMG_0412.NEF so the file is decoded as RAW.
| Formats | |
|---|---|
| Input | JPEG, PNG, WebP, AVIF, HEIC, GIF, TIFF, BMP, and camera RAW (CR2, NEF, ARW, DNG, ORF, RW2, RAF) |
| Output | jpg, png, webp, avif, heic, gif, or original |
image.format chooses the output. The default, original, keeps the format of the input where that format is a sensible output:
| Input | Output with image.format=original |
|---|---|
| JPEG | JPEG |
| PNG, BMP, TIFF | PNG |
| WebP | WebP |
| AVIF | AVIF |
| HEIC | HEIC |
| GIF | GIF |
| Camera RAW | JPEG |
Which format to choose for which use is covered in Formats.
JPEG has no transparency. When a transparent image is written as JPEG, the transparent areas become white.
Quality in practice
image.quality is a number from 0 to 100, default 75. Higher means closer to the input and a larger file. The same number goes to a different encoder for each format, so it does not produce the same file size across formats. The quality scale explains the mapping. In practice:
| Output | What quality does |
|---|---|
| JPEG | Passed to the MozJPEG encoder as its quality, unchanged. |
| AVIF | Passed to the AVIF encoder as its quality, unchanged, for colour and for transparency. At the same number an AVIF file is usually smaller than a JPEG. |
| WebP | Below 96: lossy WebP at that quality. 96 and above: lossless WebP. |
| PNG | Below 96: the colours are reduced to a palette, then the file is optimised without further loss. Lower quality allows a smaller palette. 96 and above: lossless optimisation only. |
| HEIC | Passed to the HEIC encoder as its quality. |
| GIF | No effect. GIF is compressed without a quality setting. |
If the first result is larger than the input, the API tries once more with a stronger setting (for JPEG, quality minus 20; for AVIF and WebP, quality minus 25; never below 20; for PNG, a smaller palette). The second result is used only if it is smaller than the input.
Resizing
Resizing is on by default: the image is fitted within 2000 by 2000 pixels. Set the box with image.resize.width and image.resize.height, and how the image is placed in it with image.resize.mode.
| Mode | Result | Enlarges? |
|---|---|---|
fit | The whole image, scaled down until it fits inside width by height. The aspect ratio is kept. | No |
fill | Exactly width by height. The image is scaled to cover the box and the overflow is cropped equally from both sides. | Yes, if the image is smaller than the box |
width | Scaled down until it is no wider than width. Height is ignored. | No |
height | Scaled down until it is no taller than height. Width is ignored. | No |
Width and height are clamped to the range 100 to 9999. A dimension you do not send stays at 2000. To keep the pixel dimensions of the input, turn resizing off:
curl -X POST "https://api.smolmac.com/v1/compress?image.resize=false" \
-H "Authorization: Bearer $SMOL_API_KEY" \
--data-binary @photo.jpg \
-o photo.min.jpgA square thumbnail, 400 by 400, cropped to the centre:
curl -X POST "https://api.smolmac.com/v1/compress?image.resize.mode=fill&image.resize.width=400&image.resize.height=400" \
-H "Authorization: Bearer $SMOL_API_KEY" \
--data-binary @photo.jpg \
-o thumb.jpgThe final pixel size is reported as width and height in the result object.
Metadata
By default metadata is removed from the output: camera and lens details, GPS position, timestamps, software names, XMP, IPTC and embedded thumbnails. Set image.strip_metadata=false to skip the removal step. PNG output follows its own rule, described below.
Two things that live in metadata affect how a picture looks, and the API handles both before encoding:
- Orientation. A photo taken in portrait is often stored sideways with a tag that says rotate me. The rotation is applied to the pixels, so the output displays upright without the tag.
- Colour profile. Wide-gamut images (Display P3, Adobe RGB) are converted to sRGB through their embedded profile, so colours do not shift when the profile is removed.
When the original is kept
Some images are already as small as they will get. If nothing smaller can be produced, the API returns the input and does not charge for the request.
HTTP/1.1 200 OK
Content-Type: image/jpeg
Smol-Original-Size: 48211
Smol-Output-Size: 48211
Smol-Savings-Percent: 0.0
Smol-Output-Format: jpg
Smol-Kept-Original: true
Smol-Billed: falseThis happens only when all three of these are true:
- the output would be larger than the input, after the second attempt described above;
- the output format is the format of the input;
- resizing did not change the pixel dimensions.
If you asked for a different format or a resize that changed the image, you get the converted file even when it is larger, because the input would not be what you asked for. The result then has increased: true, Smol-Savings-Percent is negative, and the request is billed.
A kept original still has its metadata removed, unless image.strip_metadata is false or the file is a PNG. That removal does not touch the pixels, and it leaves the orientation tag and the colour profile in place so the image displays as before. The output can therefore be slightly smaller than the input even when Smol-Kept-Original is true. More in Kept original.
Animation
Animated GIF and animated WebP are supported as input. What happens depends on the output format:
| Input | Output | Result |
|---|---|---|
| Animated GIF | GIF | Stays animated. The frames are optimised. Quality has no effect. |
| Animated GIF | WebP | Animated WebP at the quality you set, looping forever. Usually much smaller than the GIF. |
| Animated WebP | WebP | Stays animated, re-encoded at the quality you set. |
| Animated WebP | GIF | Animated GIF. |
| Animated GIF or WebP | JPEG, PNG, AVIF, HEIC | The first frame only, with a warning in the result. |
When frames are dropped, the result says so:
"warnings": [
"The input is animated and avif is a still format, so only the first frame was kept."
]An animation that stays an animation is not resized, and its metadata is not touched. The resize and metadata options apply to still images only. An animation may have at most 2,000 frames.
Examples
JPEG to AVIF at a set width
For a web page that shows photos at most 1200 pixels wide:
curl -X POST "https://api.smolmac.com/v1/compress?image.format=avif&image.quality=60&image.resize.mode=width&image.resize.width=1200" \
-H "Authorization: Bearer $SMOL_API_KEY" \
--data-binary @photo.jpg \
-o photo.avifPNG, smaller, same dimensions
A screenshot or an illustration, kept as PNG. At quality 75 the colours are reduced to a palette, which is where most of the saving comes from:
curl -X POST "https://api.smolmac.com/v1/compress?image.resize=false" \
-H "Authorization: Bearer $SMOL_API_KEY" \
--data-binary @screenshot.png \
-o screenshot.min.pngThe same file with lossless optimisation only. At quality 96 and above no colours are reduced:
curl -X POST "https://api.smolmac.com/v1/compress?image.quality=100&image.resize=false" \
-H "Authorization: Bearer $SMOL_API_KEY" \
--data-binary @screenshot.png \
-o screenshot.lossless.pngAnything to WebP
WebP handles photos and transparency, and every current browser displays it. This sends the options as JSON in a multipart request instead of the query string:
curl -X POST "https://api.smolmac.com/v1/compress" \
-H "Authorization: Bearer $SMOL_API_KEY" \
-F "[email protected]" \
-F 'options={"image":{"format":"webp","quality":80,"resize":false}}' \
-o logo.webpLimits and billing
- A direct request takes a file of up to 25 MB. For a larger image, upload it and create a job. See Large files and jobs.
- An image may have at most 100 megapixels, or 50 on an account with no plan yet. Above that the request fails with
422 limit_exceeded. All limits are in Limits. - Each compressed image costs $0.010 for the first 10,000 images in a calendar month and $0.0022 after that. The price does not depend on the size of the file or on the options.
- Failed requests and requests where the original was kept are free.
Smol-Billedtells you which it was.