Introduction
The petiteimg API lets you compress, resize, crop, and convert images programmatically. Send images with a set of operations and get optimized files back.
All processing happens in memory — your images are never stored on our servers.
Example request
curl -X POST https://api.petiteimg.com/v1/process \
-H "Authorization: Bearer pi_YOUR_API_KEY" \
-F "file=@photo.jpg" \
-F 'operations=[{"op":"compress","settings":{"quality":80}}]' \
--output compressed.jpg
import requests
resp = requests.post(
"https://api.petiteimg.com/v1/process",
headers={"Authorization": "Bearer pi_YOUR_API_KEY"},
files={"file": open("photo.jpg", "rb")},
data={"operations": '[{"op":"compress","settings":{"quality":80}}]'},
)
with open("compressed.jpg", "wb") as f:
f.write(resp.content)
const form = new FormData();
form.append("file", fs.createReadStream("photo.jpg"));
form.append("operations", JSON.stringify([
{ op: "compress", settings: { quality: 80 } }
]));
const res = await fetch("https://api.petiteimg.com/v1/process", {
method: "POST",
headers: { Authorization: "Bearer pi_YOUR_API_KEY" },
body: form,
});
const buffer = await res.arrayBuffer();
fs.writeFileSync("compressed.jpg", Buffer.from(buffer));
require "net/http"
require "uri"
uri = URI("https://api.petiteimg.com/v1/process")
form = [
["file", File.open("photo.jpg")],
["operations", '[{"op":"compress","settings":{"quality":80}}]']
]
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer pi_YOUR_API_KEY"
req.set_form(form, "multipart/form-data")
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
File.binwrite("compressed.jpg", res.body)
$ch = curl_init("https://api.petiteimg.com/v1/process");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer pi_YOUR_API_KEY"],
CURLOPT_POSTFIELDS => [
"file" => new CURLFile("photo.jpg"),
"operations" => '[{"op":"compress","settings":{"quality":80}}]',
],
]);
$result = curl_exec($ch);
curl_close($ch);
file_put_contents("compressed.jpg", $result);
package main
import (
"bytes"
"io"
"mime/multipart"
"net/http"
"os"
)
func main() {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
file, _ := os.Open("photo.jpg")
part, _ := writer.CreateFormFile("file", "photo.jpg")
io.Copy(part, file)
file.Close()
writer.WriteField("operations", `[{"op":"compress","settings":{"quality":80}}]`)
writer.Close()
req, _ := http.NewRequest("POST", "https://api.petiteimg.com/v1/process", body)
req.Header.Set("Authorization", "Bearer pi_YOUR_API_KEY")
req.Header.Set("Content-Type", writer.FormDataContentType())
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
out, _ := os.Create("compressed.jpg")
io.Copy(out, resp.Body)
out.Close()
}
use reqwest::multipart;
use std::fs;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let file_bytes = fs::read("photo.jpg")?;
let file_part = multipart::Part::bytes(file_bytes).file_name("photo.jpg");
let form = multipart::Form::new()
.part("file", file_part)
.text("operations", r#"[{"op":"compress","settings":{"quality":80}}]"#);
let res = reqwest::Client::new()
.post("https://api.petiteimg.com/v1/process")
.bearer_auth("pi_YOUR_API_KEY")
.multipart(form)
.send()
.await?;
fs::write("compressed.jpg", res.bytes().await?)?;
Ok(())
}
Authentication
All API requests require a Bearer token in the Authorization header. Generate API keys from your dashboard.
Authorization: Bearer YOUR_API_KEY
Requests without a valid key return 401 Unauthorized.
Base URL
The API is served on a dedicated subdomain:
https://api.petiteimg.com
Process images
/v1/process
Send a single image along with an array of operations. The API processes them in a fixed pipeline order: resize → crop → compress → convert, regardless of the order you specify.
Supported formats
| Direction | Formats |
|---|---|
| Input | JPEG, PNG, WebP, AVIF |
| Output | JPEG, PNG, WebP |
Request
Use multipart/form-data with two fields:
| Field | Type | Description |
|---|---|---|
file | File | A single image file (JPEG, PNG, WebP, or AVIF). |
operations | JSON string | Array of operation objects (see Operations). |
Response
The processed image is returned directly as binary with Content-Disposition: attachment.
The X-Process-Results header contains a JSON array with metadata for each file:
[
{
"filename": "photo_compressed.jpg",
"input_size": 2048000,
"output_size": 512000,
"savings_percent": 75.0
}
]
Example
curl -X POST https://api.petiteimg.com/v1/process \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "file=@photo.jpg" \
-F 'operations=[{"op":"resize","settings":{"width":800}},{"op":"compress","settings":{"quality":75}}]' \
--output photo_optimized.jpg
import requests
res = requests.post(
"https://api.petiteimg.com/v1/process",
headers={"Authorization": "Bearer YOUR_API_KEY"},
files={"file": open("photo.jpg", "rb")},
data={"operations": '[{"op":"resize","settings":{"width":800}},{"op":"compress","settings":{"quality":75}}]'},
)
with open("photo_optimized.jpg", "wb") as f:
f.write(res.content)
const form = new FormData();
form.append("file", fileInput.files[0]);
form.append("operations", JSON.stringify([
{ op: "resize", settings: { width: 800 } },
{ op: "compress", settings: { quality: 75 } }
]));
const res = await fetch("https://api.petiteimg.com/v1/process", {
method: "POST",
headers: { Authorization: "Bearer YOUR_API_KEY" },
body: form,
});
const blob = await res.blob();
// blob contains the processed image
require "net/http"
require "uri"
uri = URI("https://api.petiteimg.com/v1/process")
ops = '[{"op":"resize","settings":{"width":800}},{"op":"compress","settings":{"quality":75}}]'
form = [["file", File.open("photo.jpg")], ["operations", ops]]
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer YOUR_API_KEY"
req.set_form(form, "multipart/form-data")
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
File.binwrite("photo_optimized.jpg", res.body)
$ch = curl_init("https://api.petiteimg.com/v1/process");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer YOUR_API_KEY"],
CURLOPT_POSTFIELDS => [
"file" => new CURLFile("photo.jpg"),
"operations" => '[{"op":"resize","settings":{"width":800}},{"op":"compress","settings":{"quality":75}}]',
],
]);
$result = curl_exec($ch);
curl_close($ch);
file_put_contents("photo_optimized.jpg", $result);
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
file, _ := os.Open("photo.jpg")
part, _ := writer.CreateFormFile("file", "photo.jpg")
io.Copy(part, file)
file.Close()
writer.WriteField("operations",
`[{"op":"resize","settings":{"width":800}},{"op":"compress","settings":{"quality":75}}]`)
writer.Close()
req, _ := http.NewRequest("POST", "https://api.petiteimg.com/v1/process", body)
req.Header.Set("Authorization", "Bearer YOUR_API_KEY")
req.Header.Set("Content-Type", writer.FormDataContentType())
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
out, _ := os.Create("photo_optimized.jpg")
io.Copy(out, resp.Body)
out.Close()
let file_bytes = fs::read("photo.jpg")?;
let file_part = multipart::Part::bytes(file_bytes).file_name("photo.jpg");
let form = multipart::Form::new()
.part("file", file_part)
.text("operations",
r#"[{"op":"resize","settings":{"width":800}},{"op":"compress","settings":{"quality":75}}]"#);
let res = reqwest::Client::new()
.post("https://api.petiteimg.com/v1/process")
.bearer_auth("YOUR_API_KEY")
.multipart(form)
.send()
.await?;
fs::write("photo_optimized.jpg", res.bytes().await?)?;
Operations
Each operation is an object with op (the operation name) and settings (its parameters).
compress
Reduce file size with lossy compression.
| Setting | Type | Default | Description |
|---|---|---|---|
quality | integer | 80 | Output quality, 1–100. |
super_saver | boolean | false | Enable aggressive compression (slower, smaller output). |
{"op": "compress", "settings": {"quality": 75, "super_saver": true}}
resize
Downscale images to fit within a maximum width and/or height. Aspect ratio is always preserved. Images are never upscaled.
| Setting | Type | Description |
|---|---|---|
width | integer | Maximum width in pixels. Optional if height is set. |
height | integer | Maximum height in pixels. Optional if width is set. |
{"op": "resize", "settings": {"width": 1200}}
crop
Center-crop the image to exact dimensions.
| Setting | Type | Description |
|---|---|---|
width | integer | Target width in pixels. Required. |
height | integer | Target height in pixels. Required. |
{"op": "crop", "settings": {"width": 1080, "height": 1080}}
convert
Change the output format.
| Setting | Type | Description |
|---|---|---|
format | string | Target format: "jpeg", "png", or "webp". |
{"op": "convert", "settings": {"format": "webp"}}
Combining operations
Pass multiple operations in a single request. They always execute in pipeline order regardless of array order.
[
{"op": "resize", "settings": {"width": 800, "height": 600}},
{"op": "compress", "settings": {"quality": 80}},
{"op": "convert", "settings": {"format": "webp"}}
]
Presets
/v1/presets
Returns all available crop presets grouped by category. Useful for building crop UIs or automating social media exports.
[
{
"group": "Social Media",
"presets": [
{"id": "ig-post", "name": "Instagram Post", "width": 1080, "height": 1080},
{"id": "ig-story", "name": "Instagram Story", "width": 1080, "height": 1920},
{"id": "fb-post", "name": "Facebook Post", "width": 1200, "height": 630}
]
},
{
"group": "Google Ads",
"presets": [
{"id": "ad-leaderboard", "name": "Leaderboard", "width": 728, "height": 90},
{"id": "ad-med-rect", "name": "Medium Rectangle", "width": 300, "height": 250}
]
},
{
"group": "Covers",
"presets": [
{"id": "og-image", "name": "OG Image", "width": 1200, "height": 630},
{"id": "podcast-cover", "name": "Podcast Cover", "width": 3000, "height": 3000}
]
}
]
Health check
/v1/health
Returns the API status. This endpoint does not require authentication.
{"status": "ok"}
Rate limits
API usage is metered per image processed, not per request.
| Plan | API limit | Max file size |
|---|---|---|
| Free | 500 images / month | 10 MB |
| Pro | 150,000 images / year | 100 MB |
When you exceed your limit, requests return 429 Too Many Requests. Upgrade your plan for higher limits.
Errors
Errors return a JSON body with an error field:
{"error": "Monthly API limit exceeded. Upgrade your plan for more capacity."}
| Status | Meaning |
|---|---|
400 | Bad request — missing files, invalid operations JSON, or file too large. |
401 | Unauthorized — missing or invalid API key. |
429 | Rate limit exceeded. |
500 | Internal server error — processing failed. |