API Documentation
v2.0Contents
Overview
The Optimagio API provides powerful image optimization capabilities. Upload images via URL or file upload, or send a full folder as an upload-first batch, and receive optimized versions with significant size reductions while maintaining visual quality.
Base URL: https://api.optimagio.dev
Key Features
- Input support for JPEG/JPG, PNG, WebP, GIF, AVIF, TIFF, and BMP; output support for JPEG, PNG, WebP, GIF, AVIF, or auto
- URL-based and file upload optimization
- Upload-first batch optimization with automatic batch creation, progress events, and ZIP download
- Configurable compression levels and output formats
- Image resizing with dimension constraints (up to 8000px)
- EXIF metadata is stripped by default; set
keep_exif=trueto opt in - Storage Library persistence is explicit; set
storage_commit=truewhen an API request should save optimized results - Automatic format selection with
autooption - Caching of optimized results
- Dashboard and admin analytics for API-key traffic and batch activity
- Multi-tier access controls with different quotas
Authentication
All API requests require authentication using an API key. Include your API key in the request headers.
Option 1: X-API-Key Header
X-API-Key: your_api_key_hereOption 2: Authorization Header
Authorization: Bearer your_api_key_hereGet your API key: Sign up for an account and generate your API key from the dashboard.
API keys use the format opt_....
The plaintext key is returned exactly once when created; active key counts are tier-limited
(Free 1, Starter 3, Pro 10, Business 25, Scale custom by contract).
Endpoints
Optimize Image
/api/optimizeOptimize an image from URL or file upload with configurable parameters.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| url | string | Yes* | Image URL to optimize (required if not using file upload) |
| accept_file | boolean | No | Set to true for file upload mode |
| image | file | Yes* | Image file (required if accept_file=true) |
| compression | string | No | Compression level: auto, low, medium, max (default: auto) |
| width | integer | No | Target width in pixels (max 8000) |
| height | integer | No | Target height in pixels (max 8000) |
| format | string | No | Output format: jpeg, png, webp, gif, avif, auto |
| keep_exif | boolean | No | Preserve EXIF metadata (default: false) |
| storage_commit | boolean | No | Save optimized result to Storage Library (default: false) |
| keep_original | boolean | No | Retain original source bytes in Storage Library; requires storage_commit=true (default: false) |
Response
{
"success": true,
"cached": false,
"original_filename": "example.jpg",
"original_size": 1048576,
"optimized_size": 524288,
"compression_ratio": "50.0% saved",
"percent": 50,
"data": {
"id": "v2-a1b2c3d4-cmax-w1920-h1080-jpeg-exiftrue",
"src_url": "https://example.com/original.jpg",
"optimized_url": "/api/images/v2-a1b2c3d4-cmax-w1920-h1080-jpeg-exiftrue",
"size": 524288,
"width": 1920,
"height": 1080,
"format": "jpeg",
"processed_at": "2026-04-28T10:30:00Z"
}
}Caching: The id is a content + parameter hash, not a UUID. Re-submitting the same image with the same parameters returns the same id and cached: true — no recomputation cost.
Storage Library: Optimize requests do not save optimized results or originals by default. Send storage_commit=true to persist the optimized result. Add keep_original=true only when you also want the original retained; keep_original=true without storage_commit=true is rejected.
URL mode constraints: Only http / https are allowed; private and internal IP ranges are blocked; downloaded source size is capped at 50 MB. The optimization request body itself is capped at 10 MB.
Optimize Batch
/api/v1/optimize/batchUpload multiple image files directly. Optimagio creates the batch automatically, generates job_id values server-side, queues accepted files, and returns links for status, SSE progress, and ZIP download.
Simple multipart fields
| Field | Type | Required | Description |
|---|---|---|---|
| files[] | file | Yes | Repeat for each image. Up to 100 files per request. |
| output_format | string | No | Output format. Alias: format. |
| compression | string | No | Compression level such as auto, low, medium, or max. |
| resize_width | integer | No | Target width. Alias: width. |
| resize_height | integer | No | Target height. Alias: height. |
| keep_exif | boolean | No | Preserve EXIF metadata when the output encoder supports it. |
| storage_commit | boolean | No | Save accepted optimized results to Storage Library. Alias in manifest: options.storage_commit. |
| keep_original | boolean | No | Retain originals for accepted files; requires storage_commit=true. Alias in manifest: options.keep_original. |
Response
{
"batch_id": "1e57527d-2c52-4d40-95a4-4b43c438c06a",
"client_batch_id": "catalog-2026-05",
"status": "processing",
"created": true,
"finalized": true,
"accepted": 3,
"rejected": 0,
"rejected_files": [],
"files": [
{
"job_id": "c0e8bcb3-410b-485f-b5ee-2cb4493b9e78",
"client_file_id": "file_0",
"filename": "hero.jpg",
"status": "queued",
"status_url": "/api/v1/jobs/c0e8bcb3-410b-485f-b5ee-2cb4493b9e78"
}
],
"links": {
"status": "/api/v1/batches/1e57527d-2c52-4d40-95a4-4b43c438c06a",
"status_page": "/api/v1/batches/1e57527d-2c52-4d40-95a4-4b43c438c06a?files_limit=1000&files_offset=0",
"events": "/api/v1/batches/1e57527d-2c52-4d40-95a4-4b43c438c06a/events",
"download_zip": "/api/v1/batches/1e57527d-2c52-4d40-95a4-4b43c438c06a/download"
}
}Batch lifecycle: use GET /api/v1/batches/:id for status, files_limit / files_offset for large status reads, GET /api/v1/batches/:id/events for optional SSE progress, and GET /api/v1/batches/:id/download for the completed ZIP.
These routes accept API-key auth and verify batch ownership. The ZIP is the canonical bulk retrieval path; per-file download_url values are convenience links and can expire. If any completed result is missing from storage,
the ZIP endpoint returns 409 partial_batch_results_unavailable with completed, downloadable, and missing counts instead of a partial archive.
SSE update event
event: update
data: {
"kind": "file_status",
"batch_id": "1e57527d-2c52-4d40-95a4-4b43c438c06a",
"job_id": "c0e8bcb3-410b-485f-b5ee-2cb4493b9e78",
"file_status": "completed",
"original_size": 1048576,
"optimized_size": 421888,
"compression_ratio": 59.8,
"result_image_id": "v2-a1b2..."
}Get Optimized Image
/api/images/:idDownload or preview an optimized image by its ID from Optimagio or an API client. No authentication required — the image ID acts as a capability token, but browser requests from cross-site pages are rejected so these URLs cannot be used as public CDN embeds.
Returns the raw image binary with appropriate Content-Type header.
Cache-Control: private, no-cache, max-age=0, must-revalidateAccount lifecycle: Your files stay in your account as long as your subscription is active, plus 60 days after cancellation. Free accounts unused for 12 months are reminded twice and then archived. Anonymous and Free saved results outside an active account still use the short cache window. Use POST /api/images/touch only for active uncommitted browser sessions, and call POST /api/images/commit when the user explicitly saves current results.
Get Usage Limits
/api/limitsGet your current usage and quota limits. Requires API key authentication.
Response
{
"subscription_tier": "free",
"is_admin": false,
"lifecycle": {
"state": "active",
"reason": "",
"previous_tier": "",
"target_tier": "",
"storage_bytes_at_transition": 0,
"storage_cap_bytes_at_transition": 0,
"warning_count": 0
},
"storage": {
"used_bytes": 0,
"included_bytes": 0,
"remaining_bytes": 0,
"hard_limit_bytes": 0
},
"limits": {
"monthly_images": 100,
"file_size_mb": 10,
"bandwidth_bytes": 1073741824,
"minute_requests": 30,
"max_api_keys": 1,
"team_seats": 1
},
"usage": {
"monthly_images": 47,
"bandwidth_bytes": 73400320,
"minute_requests": 3,
"team_seats": 1,
"active_seats": 1,
"pending_seats": 0,
"month_start": "2026-04-01",
"month_end": "2026-04-30"
},
"remaining": {
"monthly_images": 53,
"bandwidth_bytes": 1000341504,
"minute_requests": 27,
"team_seats": 0
},
"pricing": {
"tier": "free",
"display_name": "Free",
"monthly_cents": 0,
"annual_cents": 0,
"annual_discount_percent": 15
}
}Manage Stored Results
/api/my/storage/usage/api/my/storage/items?limit=50&offset=0/api/my/storage/items/:object_id/api/my/storage/items/:object_idAuthenticated accounts can inspect stored-result usage, list committed objects, pin an existing cloud-stored optimized result into persistent account storage, or unpin/delete one stored result to reclaim bytes. Pin and unpin are dashboard mutations and require action intents. Private Browser Mode results stay local and cannot be pinned because no Optimagio object exists.
List Response
{
"items": [
{
"id": "abc123",
"image_id": "abc123",
"filename": "hero.webp",
"byte_size": 248391,
"pinned_at": "2026-05-08T12:00:00Z",
"tier_at_commit": "starter",
"lifecycle_state": "active",
"download_url": "/api/images/abc123"
}
],
"total": 1,
"limit": 50,
"offset": 0
}Bulk Download (ZIP)
/api/images/download-zipStream a ZIP archive containing many already-optimized images. No authentication required — access is controlled by the capability ids you provide. Up to 5,000 ids per request; the resulting archive is capped at 500 MB. Source ids must still be live (see retention note above).
Request Body (JSON)
{
"images": [
{ "id": "v2-a1b2c3d4-cmax-w1920-h1080-jpeg-exiftrue", "filename": "hero.jpg", "format": "jpeg" },
{ "id": "v2-e5f6g7h8-cmax-w0800-h0600-webp-exiftrue", "filename": "thumb.webp", "format": "webp" }
]
}Response
Streams application/zip with Content-Disposition: attachment; filename=optimized_images.zip and Cache-Control: no-store. Missing/expired ids are skipped; if every requested id is gone, the endpoint returns 409 no_downloadable_images.
Refresh Active Results
/api/images/touchRefresh active uncommitted browser-session results before they are saved. Committed results keep the retention window captured at commit time. Up to 500 ids per request.
Request Body (JSON)
{
"ids": [
"v2-a1b2c3d4-cmax-w1920-h1080-jpeg-exiftrue",
"v2-e5f6g7h8-cmax-w0800-h0600-webp-exiftrue"
]
}Response
{ "touched": 2 }Supported Formats
/public/formatsAuthoritative list of input/output formats and per-format capabilities. No authentication required.
Response
{
"supported_formats": [
{
"name": "WebP",
"extension": "webp",
"mime_type": "image/webp",
"description": "Modern format with superior compression",
"features": ["Superior compression", "Lossless/Lossy", "Transparency", "Animation"]
}
// ... JPEG, PNG, GIF, AVIF
],
"input_formats": ["JPEG", "PNG", "WebP", "GIF", "TIFF", "BMP"],
"output_formats": ["JPEG", "PNG", "WebP", "GIF", "AVIF"],
"max_file_size": "50MB",
"max_dimensions": "10000x10000"
}Pricing Plans
/public/pricingList of marketing plan previews with pricing and feature breakdowns. No authentication required. Useful for public pricing pages and access-request flows.
Response
{
"plans": [
{
"code": "free",
"name": "Free",
"currency": "USD",
"period": "month",
"monthly_price": 0,
"annual_price": 0,
"requests_limit": 100,
"size_limit_mb": 10,
"bandwidth_gb": 1,
"rate_limit_per_minute": 30,
"rate_limit_per_key_per_minute": 15,
"optimized_retention_days": 0,
"included_storage_bytes": 0,
"api_keys": 1,
"included_seats": 1,
"features": [...]
},
{ "code": "starter", "name": "Starter", "monthly_price": 9, ... },
{ "code": "pro", "name": "Pro", "monthly_price": 29, "popular_plan": true, ... },
{ "code": "business", "name": "Business", "monthly_price": 89, ... },
{ "code": "scale", "name": "Scale", "monthly_price": 399, "contact_sales": true, ... }
],
"annual_discount_percent": 15,
"contact": { "sales": "[email protected]", "support": "[email protected]" }
}The plan names returned by this endpoint are marketing labels. The internal subscription_tier field controls API rate limits, quotas, and access only; it is not proof of a paid subscription or billing consent. Values are free, starter, pro, business, scale — see Access Tiers.
Health Check
/api/healthCheck API service status. No authentication required.
{
"status": "ok",
"service": "optimagio-api",
"version": "2.0.0"
}Batch Uploads
For external API clients, POST /api/v1/optimize/batch is the primary batch API. Send files first; the server creates or extends the batch, generates job IDs, and returns the tracking links.
For 5000 files, send 50 chunks of up to 100 files with one stable client_batch_id and a unique Idempotency-Key per chunk.
Simple upload
curl -X POST https://api.optimagio.dev/api/v1/optimize/batch \
-H "X-API-Key:${OPTIMAGIO_API_KEY}" \
-H "Idempotency-Key: catalog-may-2026-part-1" \
-F "output_format=webp" \
-F "compression=auto" \
-F "storage_commit=true" \
-F "resize_width=2000" \
-F "files[]=@products/shoes/nike-001.jpg" \
-F "files[]=@products/shoes/nike-002.png"When no manifest is supplied, file names come from the multipart parts and the batch is finalized automatically. Use this mode for small batches and quick integrations.
Advanced manifest
Send a JSON manifest field when you need stable client IDs,
folder paths in the ZIP, checksums, chunking, or an expected total.
{
"client_batch_id": "catalog-2026-05",
"name": "May product catalog",
"total_files": 1842,
"finalize": false,
"options": {
"output_format": "webp",
"compression": "auto",
"resize_width": 2000,
"keep_exif": false,
"storage_commit": true,
"keep_original": false
},
"files": [
{
"field": "file_0",
"client_file_id": "products/shoes/nike-001.jpg",
"relative_path": "products/shoes/nike-001.jpg",
"sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
]
}curl -X POST https://api.optimagio.dev/api/v1/optimize/batch \
-H "X-API-Key:${OPTIMAGIO_API_KEY}" \
-H "Idempotency-Key: catalog-2026-part-0001" \
-F 'manifest={"client_batch_id":"catalog-2026-05","total_files":1842,"finalize":false,"options":{"output_format":"webp","compression":"auto"},"files":[{"field":"file_0","client_file_id":"products/shoes/nike-001.jpg","relative_path":"products/shoes/nike-001.jpg"}]}' \
-F "file_0=@products/shoes/nike-001.jpg"Chunked upload
| Step | Manifest fields | Behavior |
|---|---|---|
| First chunk | client_batch_id, total_files, finalize=false | Creates the batch and returns batch_id. total_files is required when creating an unfinished upload. |
| Middle chunks | client_batch_id or batch_id, finalize=false | Appends more files to the same batch. Prefer the same client_batch_id so clients do not need to persist batch_id between chunks. Each chunk should use a new Idempotency-Key. |
| Final chunk | client_batch_id or batch_id, finalize=true | Finalizes upload only when uploaded file rows match total_files. Finalized batches reject further appends. |
Idempotency
Use Idempotency-Key on every upload attempt or chunk.
Retrying the same key with the same fingerprint returns the saved response. A different payload with the
same key returns 409 idempotency_conflict. A duplicate while the first
request is still running returns retryable 409 idempotency_in_progress with Retry-After.
client_batch_id is scoped to the authenticated user and helps find the same
batch after a retry or process restart. It is not a substitute for per-chunk idempotency.
Polling and retrieval
- Use
GET /api/v1/batches/:id?files_limit=1000&files_offset=0as the primary integration path for status and result visibility. - Advance
files_offsetthrough large batches; the response reports whether more file rows are available. - SSE at
/api/v1/batches/:id/eventsremains available for live UI progress, but polling is the recommended external API baseline. - Use
download_zipfor completed batch retrieval. Completed file rows can includedownload_url, but those per-file links can expire; the batch ZIP returns409 partial_batch_results_unavailableif any completed output is missing.
Limits, paths, and accounting
- Each upload-first request accepts up to 100 files and uses the bundle upload protection bucket, not the 10 MB sync optimize bucket.
- A batch can contain up to 50,000 files; 5000 files should be split into 50 requests with 100 files each.
- Per-file size limits are tier-aware. Oversized files are rejected before queueing, recorded as failed batch rows, and consume zero optimization credits.
- Batch semantics are
per_item: invalidrelative_path, invalidsha256, checksum mismatches, and oversized files go torejected_fileswhile accepted files continue processing. - Monthly image quota is enforced by workers. A chunk can queue more files than the remaining quota; extra jobs become
quota_pausedand can resume after upgrade or reset. - Cache hits are free for monthly image credits but still appear in analytics. Result bandwidth is counted separately where bandwidth limits apply.
- Batch ZIP download preserves validated
relative_pathfolder structure. Without a safe path, files use the flat optimized filename behavior.
Path safety: folder paths must be relative, use forward slashes, and avoid empty segments, ., .., control characters, absolute paths, and excessive length.
Advanced/internal-compatible flow
Most integrations should use POST /api/v1/optimize/batch.
The explicit batch and bundle routes remain available for dashboard internals, older preflight flows,
pause/resume/cancel before upload, and custom enterprise automation.
POST /api/v1/batches
POST /api/v1/optimize/async/upload-bundle
GET /api/v1/batches/{batch_id}
GET /api/v1/batches/{batch_id}/events
GET /api/v1/batches/{batch_id}/downloadExamples
URL-based Optimization
cURL
curl -X POST https://api.optimagio.dev/api/optimize \
-H "X-API-Key:${OPTIMAGIO_API_KEY}" \
-F "url=https://example.com/image.jpg" \
-F "compression=medium" \
-F "storage_commit=true" \
-F "width=1920" \
-F "height=1080"File Upload Optimization
cURL
curl -X POST https://api.optimagio.dev/api/optimize \
-H "X-API-Key:${OPTIMAGIO_API_KEY}" \
-F "accept_file=true" \
-F "compression=max" \
-F "format=webp" \
-F "storage_commit=true" \
-F "image=@/path/to/your/image.jpg"Upload-First Batch Optimization
cURL
curl -X POST https://api.optimagio.dev/api/v1/optimize/batch \
-H "X-API-Key:${OPTIMAGIO_API_KEY}" \
-H "Idempotency-Key: product-batch-001" \
-F "output_format=webp" \
-F "compression=auto" \
-F "storage_commit=true" \
-F "resize_width=2000" \
-F "files[][email protected]" \
-F "files[][email protected]"JavaScript
const batchForm = new FormData();
const clientBatchId = 'catalog-2026-05';
batchForm.append('output_format', 'webp');
batchForm.append('compression', 'auto');
batchForm.append('storage_commit', 'true');
batchForm.append('resize_width', '2000');
batchForm.append('manifest', JSON.stringify({
client_batch_id: clientBatchId,
total_files: fileInput.files.length,
finalize: true
}));
for (const file of fileInput.files) {
batchForm.append('files[]', file, file.name);
}
const batchResponse = await fetch('https://api.optimagio.dev/api/v1/optimize/batch', {
method: 'POST',
headers: {
'X-API-Key': 'your_api_key_here',
'Idempotency-Key': clientBatchId + '-chunk-0001'
},
body: batchForm
});
const batch = await batchResponse.json();
const status = await fetch('https://api.optimagio.dev' + batch.links.status_page, {
headers: { 'X-API-Key': 'your_api_key_here' }
});
console.log(batch.client_batch_id, await status.json(), batch.links.download_zip);JavaScript (Fetch API)
// URL-based optimization
const urlForm = new FormData();
urlForm.append('url', 'https://example.com/image.jpg');
urlForm.append('compression', 'medium');
urlForm.append('format', 'webp');
urlForm.append('storage_commit', 'true'); // optional: save optimized result
const response = await fetch('https://api.optimagio.dev/api/optimize', {
method: 'POST',
headers: {
'X-API-Key': 'your_api_key_here'
},
body: urlForm
});
const result = await response.json();
console.log(result);
// File upload optimization
const formData = new FormData();
formData.append('accept_file', 'true');
formData.append('compression', 'max');
formData.append('storage_commit', 'true'); // optional: save optimized result
formData.append('image', fileInput.files[0]);
const uploadResponse = await fetch('https://api.optimagio.dev/api/optimize', {
method: 'POST',
headers: {
'X-API-Key': 'your_api_key_here'
},
body: formData
});
const uploadResult = await uploadResponse.json();Python (requests)
import requests
# URL-based optimization
response = requests.post(
'https://api.optimagio.dev/api/optimize',
headers={'X-API-Key': 'your_api_key_here'},
data={
'url': 'https://example.com/image.jpg',
'compression': 'medium',
'format': 'webp',
'storage_commit': 'true' # optional: save optimized result
}
)
result = response.json()
print(result)
# File upload optimization
with open('image.jpg', 'rb') as f:
files = {'image': f}
data = {
'accept_file': 'true',
'compression': 'max',
'format': 'webp',
'storage_commit': 'true' # optional: save optimized result
}
response = requests.post(
'https://api.optimagio.dev/api/optimize',
headers={'X-API-Key': 'your_api_key_here'},
files=files,
data=data
)
result = response.json()Error Codes
| Status Code | Error | Description |
|---|---|---|
| 400 | Bad Request | Invalid request format, missing parameters, file size limit exceeded, or keep_original=true without storage_commit=true |
| 401 | Unauthorized | Invalid, missing, or expired API key |
| 402 | Payment Required | Monthly image, bandwidth, or stored-result cap exhausted — upgrade or wait until reset |
| 403 | Forbidden | Account suspended or insufficient permissions |
| 409 | Conflict | Idempotency conflict, in-progress duplicate upload, or append to a finalized batch |
| 404 | Not Found | Requested image not found or expired |
| 413 | Payload Too Large | File exceeds your tier's max file size — upgrade for larger uploads |
| 429 | Too Many Requests | Per-minute rate limit hit — wait, then retry |
| 500 | Internal Server Error | Server error during image processing |
| 503 | Service Unavailable | Worker pool saturated or readiness probe reports overload |
Error response format
Quota and rate-limit responses (402, 413, 429) carry a rich JSON body so clients can show the right remediation without parsing strings.
Generic error (4xx / 5xx)
{
"error": "error_code",
"message": "Human-readable explanation"
}Anonymous or global rate limit hit (429)
{
"error": "rate_limit_exceeded",
"message": "Rate limit exceeded: 60/60 requests this minute. Try again in 24 seconds.",
"limit": 60,
"used": 60,
"remaining": 0,
"reset_at": "2026-04-29T12:35:00Z",
"retry_after_seconds": 24,
"current_tier": "free",
"next_tier": "starter"
} Plus headers X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After.
Monthly quota exhausted (402)
{
"error": "monthly_quota_exceeded",
"message": "Monthly image quota exhausted: 100/100 images used.",
"limit": 100,
"used": 100,
"remaining": 0,
"reset_at": "2026-05-01T00:00:00Z",
"retry_after_seconds": null,
"current_tier": "free",
"next_tier": "starter",
"upgrade_url": "https://optimagio.com/pricing"
} Bandwidth quota uses "error": "bandwidth_quota_exceeded" with the same fields.
It is based on accepted optimized result bytes handled by Optimagio.
Stored-result cap reached (402)
{
"error": "storage_cap_reached",
"message": "Storage cap reached for your current tier.",
"limit": 10737418240,
"used": 10737418240,
"remaining": 0,
"cap_bytes": 10737418240,
"used_bytes": 10737418240,
"current_tier": "starter",
"next_tier": "pro",
"upgrade_url": "https://optimagio.com/pricing"
} This limit applies when committing optimized results into Optimagio storage. The cap is a hard limit; upgrade your plan or delete older results to free space.
Idempotency conflict (409)
{
"error": "idempotency_conflict",
"message": "Idempotency-Key was already used with a different request payload."
} In-progress duplicates return "error": "idempotency_in_progress", "retryable": true, and a Retry-After header.
File too large (413)
{
"error": "file_size_exceeded",
"message": "File size 50000000 bytes exceeds free tier limit of 10485760 bytes.",
"limit": 10485760,
"used": 50000000,
"current_tier": "free",
"next_tier": "starter"
}Backoff guidance: on 429 wait retry_after_seconds before retrying. On 402 / 413 retrying won't help — surface the message and link upgrade_url if present.
On 503 read the Retry-After header.
Rate Limits
Throughput is protected by four layered systems. Each runs independently; whichever rejects first wins.
API-key optimization is always cloud/accounted. In the web UI, users on paid access tiers can choose Cloud only or Private Browser Mode. Private Browser Mode stays on-device with no dashboard history, server analytics, platform result, or cloud fallback. Verified Reports use Cloud only platform results. Any result accepted by Optimagio's optimization endpoints counts as a platform result.
Upload-first batches use the bundle upload protection profile so one request can carry multiple files. This is separate from the 10 MB sync optimize body cap. Individual files still must fit the caller's tier.
Layer 1 — Edge / per-IP protection
Each route family has a per-IP cap with sustained-violation IP blocking.
Anonymous/global rate-limit hits return 429 with Retry-After when a retry delay is available.
Layer 2 — Per-account result window
A strict fixed-minute window per user. Successful cloud optimizations consume rate_limit_per_minute slots and monthly image credits.
Cache hits are recorded in analytics but are free for monthly image credits. When the
window is full, authenticated work waits or moves to the queue instead of returning a user-facing rate error.
Batch uploads follow partial drain quota policy: accepted chunks may queue more files than the remaining
monthly image quota, but workers do not process or bill beyond the limit. Extra jobs stay quota_paused until quota resets or the account receives more capacity.
Layer 3 — Per-API-key bucket
In addition to the per-user window, each API key has its own cap at ~50% of the user-level rate by default. One bombing key won't starve the user's other keys.
Layer 4 — Worker pool / readiness
When the worker pool's queue load goes over 90%, the /api/healthz/ready probe returns 503
so upstream load balancers route around the node. Free-tier jobs may also
have their compression auto-bumped under load (adaptive quality).
Per-minute result rate by tier
- Free: 30 / min · 15 / min per key
- Starter: 300 / min · 150 / min per key
- Pro: 1,000 / min · 500 / min per key
- Business: 3,000 / min · 1,500 / min per key
- Scale: 10,000 / min · 5,000 / min per key
Rate-limit headers
Responses from rate-limited routes include current counters:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 47
X-RateLimit-Reset: 1761696000
Retry-After: 24 (only on 429)Backoff guidance: authenticated optimize work is throttled by
waiting or queueing. On anonymous/global 429,
wait for Retry-After. On 402 retrying won't help — the user must upgrade or wait until reset_at. On 503 read the Retry-After header.
Async optimization
For URL-based optimization that may take seconds (large remote fetches, heavy AVIF encodes, queue contention), submit the job asynchronously and poll for status. Frees up the HTTP connection immediately and the same tier limits and quotas apply.
For file batches, prefer Upload-First Batch Optimization. The older explicit batch + upload-bundle flow remains available as an advanced/internal-compatible path for clients that need preflight control.
Submit a job
POST /api/v1/optimize/async
X-API-Key: your_key
Content-Type: application/json
{
"url": "https://example.com/photo.jpg",
"compression": "auto",
"format": "webp",
"width": 1200
}Returns 202 Accepted with the job id:
{
"job_id": "8f3c6d2e-1b4a-4d3e-9c8f-a4b3e2d1c0e1",
"status": "queued",
"status_url": "/api/v1/jobs/8f3c6d2e-1b4a-4d3e-9c8f-a4b3e2d1c0e1"
}Poll for status
GET /api/v1/jobs/{'{'}job_id{'}'}
X-API-Key: your_keyStatus progression: queued → processing → completed | failed. Batch file rows can also report quota_paused when monthly image quota is exhausted before the worker starts that file.
{
"id": "8f3c6d2e-...",
"status": "completed",
"progress": 100,
"result_image_id": "abc123...",
"result_url": "/api/images/abc123...",
"created_at": 1761696000,
"updated_at": 1761696012
}Polling cadence: 1–2 seconds is a reasonable interval.
Jobs auto-expire after 24 hours. The result image is served via /api/images/{id} just like sync responses.
Advanced/internal-compatible file bundle route
Low-level clients can explicitly create a batch and submit a preallocated bundle, but this is not the main external API path.
POST /api/v1/batches
POST /api/v1/optimize/async/upload-bundleReports and Site Audit
Report and Site Audit APIs use dashboard JWT sessions. Shared report reads are public only for callers holding the generated token. Optimization reports store metadata, not optimized image bytes.
Create a metadata report
POST /api/v1/reports
Authorization: Bearer dashboard_jwt
Content-Type: application/json
{
"title": "Homepage batch",
"processing_mode": "browser_only",
"report_sync_mode": "sync",
"files": [
{
"name": "hero.jpg",
"original_size": 1048576,
"optimized_size": 421888,
"status": "completed",
"processed_by": "local_wasm"
}
]
}{
"status": "generated",
"reused": false,
"report": {
"id": "6d1f2c6a-4d2c-4216-8c99-c3f9ef119a10",
"title": "Homepage batch",
"verification_status": "browser_reported",
"processing_mode": "browser_only",
"file_count": 1,
"saved_bytes": 626688,
"cloud_credits_used": 0,
"cloud_credits_saved": 1
}
}Create a report share link
POST /api/v1/reports/{report_id}/share
Authorization: Bearer dashboard_jwt{
"id": "72b0c734-c1af-4be8-a44c-3e3cd64f28b7",
"report_id": "6d1f2c6a-4d2c-4216-8c99-c3f9ef119a10",
"token": "show-once-share-token",
"url": "/reports/shared/show-once-share-token",
"created_at": "2026-05-07T10:30:00Z"
}The plaintext token is returned only in this create response. Public shared report payloads expose an allowlist of summary and file metadata; internal session IDs, result image IDs, hashes, and raw options are omitted.
Submit a Site Audit
POST /api/v1/site-audits
Authorization: Bearer dashboard_jwt
Content-Type: application/json
{
"url": "https://example.com",
"max_pages": 10
}{
"id": "9b4e6762-334b-4977-87fb-6d5e77db13f1",
"status": "queued",
"status_url": "/api/v1/site-audits/9b4e6762-334b-4977-87fb-6d5e77db13f1",
"max_pages": 10,
"pages_limit": 25
}Read Site Audit status
GET /api/v1/site-audits/{audit_id}
Authorization: Bearer dashboard_jwt{
"id": "9b4e6762-334b-4977-87fb-6d5e77db13f1",
"root_url": "https://example.com",
"status": "completed",
"stage": "finalizing",
"score": 74,
"pages_scanned": 5,
"images_found": 42,
"fixable_images": 18,
"potential_savings_bytes": 8388608,
"can_optimize": true,
"images": [],
"recommendations": [],
"top_issues": []
}Public audits use POST /api/public/site-audits and return a tokenized status URL. Authenticated concurrency hits return 429 audit_concurrency_exceeded with Retry-After.
Access / Quota Tiers
Your access tier determines monthly image quota, monthly bandwidth,
stored-result cap, max file size, and rate limits. The canonical internal identifier returned by GET /api/limits is subscription_tier; it is an access
and quota field, not billing consent or proof of a paid subscription. Values are: free, starter, pro, business, scale.
Monthly bandwidth means accepted optimized result bytes handled by Optimagio.
Included delivery is EU/NA Standard or approved Volume; high-cost global
Standard delivery requires explicit Global Delivery opt-in; metered quota billing is not available.
MB, GB, and TB quota labels use decimal SI bytes; JSON byte fields stay raw bytes.
| Feature | Free | Starter | Pro | Business | Scale |
|---|---|---|---|---|---|
| Identifier | free | starter | pro | business | scale |
| Monthly images | 100 | 10,000 | 50,000 | 100,000 | 500,000+ |
| Monthly bandwidth | 1 GB | 50 GB | 300 GB | 1 TB | 3 TB+ contract |
| Stored results | 0 GB included; 1h cache | 5 GB hard cap | 50 GB hard cap | 250 GB hard cap | 2 TB+ contract |
| Max file size | 10 MB | 25 MB | 100 MB | 150 MB | custom |
| Result rate | 30/min | 300/min | 1,000/min | 3,000/min | 10,000/min |
| Per-key rate | 15/min | 150/min | 500/min | 1,500/min | 5,000/min |
| API keys | 1 | 3 | 10 | 25 | custom |
Check your current usage and limits via GET /api/limits with an API key, GET /api/my/limits from an authenticated dashboard session, or from the dashboard UI. Live values come from GET /public/pricing.
Legacy tier names (premium, enterprise, unlimited) still resolve correctly to their modern
replacements but are no longer documented in plans.