Developers
Image API
Compress, convert and resize images from your own website, app or Shopify store. One request in, one optimized image out.
Quick start
- Create a free account, then open Dashboard → API and create a key.
- Store the key on your server, for example in an environment variable. It starts with
pnx_. - Send an image:
curl -X POST "https://pixnex.vercel.app/api/v1/optimize?quality=75&format=webp" \
-H "Authorization: Bearer $PIXNEX_API_KEY" \
-H "Content-Type: image/jpeg" \
--data-binary @photo.jpg \
-o photo.webp -D -The response is the optimized image. The headers tell you how much was saved and how many of today's images you've used.
Your images
Unlike the website tools, which run in your browser, the API processes images on our servers. Each image is held in memory only while it's being processed, then discarded. Images are never stored, logged or used for anything else. Metadata such as camera details and GPS location is removed from the result.
Authentication
Send your key in the Authorization header: Authorization: Bearer pnx_…. Keys are shown once when you create them. We only keep a fingerprint, so a lost key can't be recovered: revoke it and create a new one. You can have up to five active keys.
Never put a key in browser code, a Shopify theme or a mobile app: anyone could copy it and use your images. The API doesn't accept requests from web pages (there are no CORS headers), so calls must come from your server.
Optimize an image
POST /api/v1/optimize
Send the image as the request body with its Content-Type (for example image/png), or as multipart/form-data with a field named image. JPEG, PNG, WebP and AVIF are accepted, up to 4 MB and 50 megapixels.
Options go in the query string:
| Option | Values | Default |
|---|---|---|
format | keep, jpeg, png, webp, avif | keep (same as the input) |
quality | 1 to 100. Lower is smaller, with more visible loss. | 80 |
width, height | 1 to 10,000 pixels. Images are never enlarged. | Original size |
fit | inside keeps the whole image within the size; cover fills the size exactly and crops the rest (needs width and height). | inside |
only_smaller | true returns your original, unchanged, when re-encoding in the same format wouldn't make it smaller. | true |
Response headers
| Header | Meaning |
|---|---|
X-Pixnex-Result | optimized (smaller), original (your image, unchanged, because it couldn't be made smaller) or larger (converted or resized as asked, but bigger than the input). We never hide a size increase. |
X-Pixnex-Input-Bytes, X-Pixnex-Output-Bytes | File sizes before and after. |
X-Pixnex-Saved-Percent | How much smaller the result is. Negative when it's larger. |
X-Pixnex-Width, X-Pixnex-Height | Size of the result in pixels. |
X-Pixnex-Daily-Used, X-Pixnex-Daily-Limit | Images used today and your plan's daily limit (unlimited on Grow). |
Check your usage
GET /api/v1/usage returns today's usage. It doesn't count as an image.
{ "used": 12, "limit": 100, "remaining": 88, "resets_in_seconds": 30215 }limit and remaining are null on plans without a daily limit.
Limits
- Each successful request counts as one image toward your plan's daily limit, shared with the website tools. Failed requests don't count. Limits reset at midnight UTC. See the limits for each plan.
- Up to 60 requests a minute per key.
- Images up to 4 MB and 50 megapixels.
Errors
Errors return JSON: { "error": { "code": "…", "message": "…" } }. The message is written to be shown to a person.
| Status | Code | What to do |
|---|---|---|
| 400 | invalid_option, missing_image | Fix the request; the message says what's wrong. |
| 401 | unauthorized | The key is missing, mistyped or revoked. |
| 403 | account_suspended | Contact us. |
| 413 | file_too_large | Send an image of 4 MB or less. |
| 415 | unsupported_media_type | Set an image Content-Type, or use multipart form data. |
| 422 | invalid_image | The file isn't a supported image, is damaged or is over 50 megapixels. |
| 429 | rate_limited, daily_limit_reached | Wait for the number of seconds in the Retry-After header. |
| 503 | unavailable | Try again shortly. |
Examples
Node.js
import { readFile, writeFile } from "node:fs/promises";
const res = await fetch("https://pixnex.vercel.app/api/v1/optimize?quality=75&width=1600", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.PIXNEX_API_KEY}`, "Content-Type": "image/jpeg" },
body: await readFile("photo.jpg"),
});
if (!res.ok) throw new Error((await res.json()).error.message);
await writeFile("photo-small.jpg", Buffer.from(await res.arrayBuffer()));
console.log(res.headers.get("x-pixnex-result"), res.headers.get("x-pixnex-saved-percent") + "%");PHP
<?php
$ch = curl_init("https://pixnex.vercel.app/api/v1/optimize?format=webp");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("PIXNEX_API_KEY"), "Content-Type: image/png"],
CURLOPT_POSTFIELDS => file_get_contents("logo.png"),
CURLOPT_RETURNTRANSFER => true,
]);
$image = curl_exec($ch);
if (curl_getinfo($ch, CURLINFO_RESPONSE_CODE) === 200) file_put_contents("logo.webp", $image);Python
import os, requests
with open("photo.jpg", "rb") as f:
res = requests.post(
"https://pixnex.vercel.app/api/v1/optimize",
params={"quality": 75},
headers={"Authorization": f"Bearer {os.environ['PIXNEX_API_KEY']}", "Content-Type": "image/jpeg"},
data=f,
)
res.raise_for_status()
open("photo-small.jpg", "wb").write(res.content)Shopify
Our free script compresses the product images already in your Shopify store and replaces them in place, so image URLs, product pages and themes don't change. It runs on your computer or server; your store details never reach us. Only the images are sent to the API.
1. Create a Shopify app for your store
- Open the Shopify Dev Dashboard with the account that owns your store, and create an app.
- In the app's version settings, choose the Admin API access scopes
read_products,read_filesandwrite_files, then release the version. - Install the app on your store.
- In App settings, copy the Client ID and Client secret. Treat the secret like a password.
Already have an older custom app made in the Shopify admin with those scopes? You can use its Admin API access token (shpat_…) instead.
2. Run the script
You need Node.js 18 or later. Download pixnex-shopify.mjs and, in the same folder, run:
export PIXNEX_API_KEY="pnx_…"
export SHOPIFY_STORE="your-store.myshopify.com"
export SHOPIFY_CLIENT_ID="…"
export SHOPIFY_CLIENT_SECRET="…"
# Dry run: shows what would be saved, changes nothing
node pixnex-shopify.mjs
# Replace the images in your store
node pixnex-shopify.mjs --applyOn Windows PowerShell, set each variable with $env:PIXNEX_API_KEY = "pnx_…" instead of export.
Options and safety
--limit 50: how many images to process in one run. Each uses one of your daily images, so keep it within your plan's daily limit.- A dry run changes nothing in your store, but each image it checks still uses one of your daily images, because it's really compressed to measure the saving.
--quality 80: lower makes smaller files. Check a few products after a test run before going lower.--max-width 2048: also shrinks very large images. Shopify themes rarely show product images wider than 2048 pixels.- Before replacing an image, the script saves the original in a
pixnex-backupfolder. To undo, upload it again in Shopify. - An image is only replaced when the result is smaller. Finished images are listed in
pixnex-shopify-done.jsonand skipped next time, so nothing is compressed twice. - JPEG, PNG and WebP product images are supported, up to 4 MB each. The script keeps each image's format.
The script is plain JavaScript you can read before running. It talks only to your store and to Pixnex.
Questions
See the FAQ or contact us.