Resize, convert, compress and strip metadata — chained in a single request. A cheap, simple image API for developers who don't want to run their own pipeline.
1,000 images/month free. Images are processed and discarded — never stored.
# one call: 800px wide, WebP q80, EXIF/GPS stripped
curl -X POST https://api.purlo.dev/v1/image \
-H "Authorization: Bearer purlo_YOUR_KEY" \
-F 'operations={"resize":{"w":800},"format":"webp","quality":80,"strip":true}' \
-F "image=@photo.jpg" \
-o out.webp
One chainable pipeline
You don't need seven single-purpose tools. Send one operations object; Purlo applies them in a fixed, documented order and returns the finished image.
Fit to width/height with fit modes. Never upscales.
Output webp, avif, jpeg or png.
quality 1–100 via mozjpeg & friends.
Drops EXIF/GPS; keeps the ICC colour profile.
Copy, paste, ship
The same call — resize to 800px, WebP at quality 80, strip metadata — in the language you already use.
curl -X POST https://api.purlo.dev/v1/image \
-H "Authorization: Bearer purlo_YOUR_KEY" \
-F 'operations={"resize":{"w":800},"format":"webp","quality":80,"strip":true}' \
-F "image=@photo.jpg" \
-o out.webp
const form = new FormData();
form.append('operations', JSON.stringify({
resize: { w: 800 }, format: 'webp', quality: 80, strip: true,
}));
form.append('image', fileBlob, 'photo.jpg');
const res = await fetch('https://api.purlo.dev/v1/image', {
method: 'POST',
headers: { Authorization: 'Bearer purlo_YOUR_KEY' },
body: form,
});
const out = new Uint8Array(await res.arrayBuffer());
$ch = curl_init('https://api.purlo.dev/v1/image');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer purlo_YOUR_KEY'],
CURLOPT_POSTFIELDS => [
'operations' => '{"resize":{"w":800},"format":"webp","strip":true}',
'image' => new CURLFile('photo.jpg'),
],
CURLOPT_RETURNTRANSFER => true,
]);
file_put_contents('out.webp', curl_exec($ch));
import json, requests
ops = {"resize": {"w": 800}, "format": "webp", "quality": 80, "strip": True}
with open("photo.jpg", "rb") as image:
r = requests.post(
"https://api.purlo.dev/v1/image",
headers={"Authorization": "Bearer purlo_YOUR_KEY"},
data={"operations": json.dumps(ops)},
files={"image": image},
)
open("out.webp", "wb").write(r.content)
Prefer a client? npm install @purlo/cli — a zero-dependency library and purlo CLI.
Privacy by default
Purlo is stateless. Every upload is processed in memory or a temp path and deleted the moment the response is sent. We keep accounts, API keys and usage counts — never image data.
Read the privacy noteSimple, volume-based
Start free, upgrade when you outgrow it, cancel any time from the self-service portal. Prices in GBP.
Hit your limit and you get a clear 429 with an upgrade link — no surprise overage bills.
Five minutes to your first call
Enter your email, click the magic link, and your key is issued. No card, 1,000 images a month.