PixCrush API v1 — compress images with a single HTTP request.
All API requests require authentication. PixCrush supports two methods:
Use api as the username and your API key as the password. Compatible with TinyPNG client libraries.
Pass your API key in the Authorization header as a Bearer token.
# HTTP Basic Auth
curl --user api:YOUR_API_KEY \
--data-binary @photo.jpg \
https://api.pixcrush.dev/v1/shrink
# Bearer Token
curl -H "Authorization: Bearer YOUR_API_KEY" \
--data-binary @photo.jpg \
https://api.pixcrush.dev/v1/shrinkCompress an image. Send the file as a binary body or as a multipart file field.
| Header | Values | Default |
|---|---|---|
| X-Preset | lossless | balanced | maximum | balanced |
| X-Quality | 1-100 (overrides preset) | — |
| X-Output-Format | jpeg | png | webp | avif | same as input |
| X-Filename | Original filename hint | upload |
| Field | Type | Description |
|---|---|---|
| file | binary | The image file to compress |
| preset | string | Quality preset (alternative to header) |
| quality | integer | Quality 1-100 (alternative to header) |
| output_format | string | Target format (alternative to header) |
# Binary body
curl --user api:YOUR_API_KEY \
--data-binary @input.jpg \
-H "X-Preset: maximum" \
-H "X-Output-Format: webp" \
https://api.pixcrush.dev/v1/shrink
# Multipart upload
curl --user api:YOUR_API_KEY \
-F file=@input.jpg \
-F preset=maximum \
-F output_format=webp \
https://api.pixcrush.dev/v1/shrink{
"id": "a1b2c3d4-...",
"status": "pending",
"input": {
"size": 245000,
"format": "jpeg",
"filename": "photo.jpg"
},
"output_format": "jpeg",
"preset": "balanced"
}Check the status of a compression job. Poll this endpoint until status is completed or failed. Recommended polling interval: 1 second.
curl --user api:YOUR_API_KEY \
https://api.pixcrush.dev/v1/jobs/a1b2c3d4-...{
"id": "a1b2c3d4-...",
"status": "processing",
"input": { "size": 245000, "format": "jpeg" }
}{
"id": "a1b2c3d4-...",
"status": "completed",
"input": { "size": 245000, "format": "jpeg" },
"output": {
"size": 98000,
"savings_percent": 60.0,
"url": "/api/v1/output/a1b2c3d4-...",
"expires_at": "2026-02-23T08:00:00Z",
"kept_larger_for_provenance": false,
"metadata": {
"exif_stripped": true,
"gps_stripped": true,
"ai_provenance_stripped": true,
"icc_preserved": true,
"warnings": []
}
}
}metadata reports what was actually removed from the image. warnings is always an array — gif-extensions-failed means the GIF post-pass did not complete, so a C2PA extension may remain. kept_larger_for_provenance is true when the output is deliberately larger than the input because the smaller candidate still carried provenance — it explains a negative savings_percent that would otherwise look like an encoder regression.
Both metadata and kept_larger_for_provenance are null for jobs completed before these fields existed, or when the worker returned no report. null means not recorded, not "nothing was stripped" — treating it as falsy will tell you provenance survived when in fact nobody checked.
{
"id": "a1b2c3d4-...",
"status": "failed",
"input": { "size": 245000, "format": "jpeg", "filename": "photo.jpg" },
"output_format": "jpeg",
"preset": "balanced",
"error": "Unable to compress image"
}Download the compressed image binary. Output URLs expire 24 hours after creation. The response includes a Content-Disposition header with the original filename.
curl --user api:YOUR_API_KEY \
--output compressed.jpg \
https://api.pixcrush.dev/v1/output/a1b2c3d4-...| Header | Description | Example |
|---|---|---|
| Content-Type | MIME type of the compressed image | image/jpeg |
| Content-Disposition | Suggested filename for download | attachment; filename="photo.jpg" |
| Content-Length | Size in bytes of the compressed image | 98000 |
Check your monthly compression quota and usage.
curl --user api:YOUR_API_KEY \
https://api.pixcrush.dev/v1/usage{
"monthly_limit": 500,
"compression_count": 42,
"remaining": 458,
"resets_at": "2026-02-28T23:59:59Z"
}Manage API keys via session-authenticated routes. These endpoints require a browser session (Sanctum cookie auth), not API key auth.
| Method | Endpoint | Description |
|---|---|---|
| POST | /v1/keys | Create a new key — body: {"name": "My App"} |
| GET | /v1/keys | List all keys for the authenticated user |
| DELETE | /v1/keys/{id} | Revoke (delete) a key |
Every API response includes rate limit headers so you can track your usage in real time.
| Header | Description | Example |
|---|---|---|
| X-RateLimit-Limit | Monthly compression limit | 500 |
| X-RateLimit-Remaining | Remaining compressions this period | 458 |
| X-RateLimit-Reset | Unix timestamp when the quota resets | 1740787199 |
| Compression-Count | Total compressions used this month | 42 |
When you exceed your monthly limit, the API returns a 429 status with an error type of QuotaExceeded. Check the X-RateLimit-Reset header to know when your quota resets.
{
"error": "QuotaExceeded",
"message": "Monthly compression limit reached. Resets at 2026-03-01T00:00:00Z"
}All error responses return a JSON body with an error type and a human-readable message.
| Status | Error Type | Description |
|---|---|---|
| 400 | InvalidImage | No image data or file is unreadable |
| 400 | UnsupportedFormat | Image format is not supported |
| 400 | FileTooLarge | File exceeds 10 MB size limit |
| 401 | Unauthorized | Missing or invalid API key |
| 404 | NotFound | Job or resource does not exist |
| 410 | Expired | Output URL has expired (24 hours) |
| 429 | QuotaExceeded | Monthly compression limit reached |
| 500 | CompressionFailed | Internal compression error |
{
"error": "InvalidImage",
"message": "The uploaded file could not be read as an image."
}Presets provide sensible quality defaults per format. Use the X-Preset header or the X-Quality header (1-100) to override.
losslessVisually identical output. Maximum file size.
balancedGood balance of quality and compression. Recommended for most use cases.
maximumSmallest file size. Some visible quality loss.