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.

Quick start: Get an API key from your dashboard, then send a POST request to the process endpoint with your images and desired operations.

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.

Header
Authorization: Bearer YOUR_API_KEY

Requests without a valid key return 401 Unauthorized.

Base URL

The API is served on a dedicated subdomain:

Base URL
https://api.petiteimg.com

Process images

POST /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

DirectionFormats
InputJPEG, PNG, WebP, AVIF
OutputJPEG, PNG, WebP
AVIF note: AVIF is accepted as input but is not available as an output format. AVIF files are automatically converted to JPEG unless a different output format is specified via the convert operation.

Request

Use multipart/form-data with two fields:

FieldTypeDescription
fileFileA single image file (JPEG, PNG, WebP, or AVIF).
operationsJSON stringArray 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:

X-Process-Results
[
  {
    "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.

SettingTypeDefaultDescription
qualityinteger80Output quality, 1–100.
super_saverbooleanfalseEnable aggressive compression (slower, smaller output).
Example
{"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.

SettingTypeDescription
widthintegerMaximum width in pixels. Optional if height is set.
heightintegerMaximum height in pixels. Optional if width is set.
Example
{"op": "resize", "settings": {"width": 1200}}

crop

Center-crop the image to exact dimensions.

SettingTypeDescription
widthintegerTarget width in pixels. Required.
heightintegerTarget height in pixels. Required.
Example
{"op": "crop", "settings": {"width": 1080, "height": 1080}}

convert

Change the output format.

SettingTypeDescription
formatstringTarget format: "jpeg", "png", or "webp".
Example
{"op": "convert", "settings": {"format": "webp"}}

Combining operations

Pass multiple operations in a single request. They always execute in pipeline order regardless of array order.

Resize + compress + convert to WebP
[
  {"op": "resize", "settings": {"width": 800, "height": 600}},
  {"op": "compress", "settings": {"quality": 80}},
  {"op": "convert", "settings": {"format": "webp"}}
]

Presets

GET /v1/presets

Returns all available crop presets grouped by category. Useful for building crop UIs or automating social media exports.

Response (abbreviated)
[
  {
    "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

GET /v1/health

Returns the API status. This endpoint does not require authentication.

Response
{"status": "ok"}

Rate limits

API usage is metered per image processed, not per request.

PlanAPI limitMax file size
Free500 images / month10 MB
Pro150,000 images / year100 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 response
{"error": "Monthly API limit exceeded. Upgrade your plan for more capacity."}
StatusMeaning
400Bad request — missing files, invalid operations JSON, or file too large.
401Unauthorized — missing or invalid API key.
429Rate limit exceeded.
500Internal server error — processing failed.