Create an API key
Open API keys, create a key with images:write and images:read. Add webhooks:manage only if you will use webhooks, then copy the key immediately. The full key is shown only once.
img_live_… key in browser code or a public repository.export PIXMENDER_API_KEY="img_live_your_key"Request a secure upload URL
Create an asset record first. Use the exact MIME type of the file: JPEG, PNG, WebP, AVIF, GIF, HEIC, or HEIF. Files can be up to 20 MB.
curl --request POST https://api.pixmender.com/v1/uploads \
--header "Authorization: Bearer $PIXMENDER_API_KEY" \
--header "Content-Type: application/json" \
--data '{"content_type":"image/jpeg","filename":"photo.jpg"}'{
"id": "ast_01...",
"status": "pending_upload",
"upload_url": "https://storage.pixmender.com/...",
"upload_method": "POST",
"upload_fields": {
"bucket": "pixmender-assets",
"key": "originals/usr_.../ast_01....jpg",
"Content-Type": "image/jpeg",
"Policy": "...",
"X-Amz-Signature": "..."
},
"upload_url_expires_at": "2026-08-25T10:30:00.000Z",
"asset_expires_at": "2026-08-26T10:15:00.000Z"
}upload_url_expires_at is the short upload-policy deadline. asset_expires_at is the separate retention deadline for the uploaded image.
Upload the image bytes
POST a multipart/form-data body directly to upload_url. Add every value from upload_fields first and the image as the final file field. The signed policy accepts 1 byte through 20 MB. Do not send your PixMender API key to the storage URL.
export UPLOAD_URL="https://storage.pixmender.com/..."
# Add every upload_fields entry returned above before the file field.
curl --request POST "$UPLOAD_URL" \
--form-string "bucket=$UPLOAD_BUCKET" \
--form-string "key=$UPLOAD_KEY" \
--form-string "Content-Type=image/jpeg" \
--form-string "Policy=$UPLOAD_POLICY" \
--form-string "X-Amz-Algorithm=$UPLOAD_ALGORITHM" \
--form-string "X-Amz-Credential=$UPLOAD_CREDENTIAL" \
--form-string "X-Amz-Date=$UPLOAD_DATE" \
--form-string "X-Amz-Signature=$UPLOAD_SIGNATURE" \
--form "[email protected];type=image/jpeg"Create a processing job
Pass the uploaded asset ID and an ordered operations array. The exact operation cost is reserved and automatically returned if processing fails. Optimization costs 0.1 credit alone and is free with another operation. Upscale costs 0.5 credit, and every other operation costs 1 credit. The API supports PNG, JPEG, WebP, and AVIF as final output formats. The Idempotency-Key header is required: reuse the same stable key only for an identical request so transport retries cannot create a second charged job while the key is valid.
New keys stay valid while the job is active and for 24 hours after success, failure, or cancellation. Retries do not extend this period. Reusing an expired key creates a new request with normal processing charges. Keys issued before this TTL policy retain their unlimited lifetime. Job history and file retention are independent of the key lifetime.
curl --request POST https://api.pixmender.com/v1/images/process \
--header "Authorization: Bearer $PIXMENDER_API_KEY" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: request-001" \
--data '{
"input": { "asset_id": "ast_01..." },
"operations": [
{ "type": "denoise" },
{
"type": "upscale",
"scale": 4,
"model": "realesrgan-x4plus"
},
{ "type": "optimize" }
],
"output_format": "webp",
"compression_preset": "balanced"
}'output_size: { width: 2560, height: 2560 } to produce exactly that size with a centered crop. Dimensions are integers from 1 to 10000. With both dimensions, the default is cover. With only one dimension, the default is inside and the other side is calculated proportionally. Explicit output_size.fit: "inside" fits the whole image within the bounds without cropping. Both modes enlarge small images when needed and preserve proportions. Resizing runs after AI operations and before final encoding and optimization. Static images only.Resize alone costs 0.1 credit: send an empty operations array with output_size. With any other operation, resizing is free: optimize with resize costs 0.1 credit; upscale with optimize and resize costs 0.5 credits. The same price applies if the image already fits. Failed or canceled jobs refund the full reservation. Each new size or fit mode requires a new idempotency key; retries of the same request reuse its key.
compression_preset:lossless, balanced, or small. Omitting it keeps the existing balanced default and quality guard.Without encoder_version, the API uses the existing production encoder locally or on compatible workers. To opt into the beta, send top-level encoder_version: "v8" with an API key. Beta optimization runs only locally; preceding AI operations retain their configured providers. Its SSIMULACRA2 floors are 60 / 75 / 90 for Compact / Balanced / Visual Lossless, with a 120-second optimization budget. Production retains its existing quality guard and configured limits.
{
"id": "job_01...",
"status": "queued",
"credits_reserved": 1.5,
"input_asset_id": "ast_01...",
"result_asset_id": null
}Poll the job status
Poll every 1–3 seconds until the status is succeeded,failed, canceled, or expired. On success, use result_asset_id for the download request. Optimized jobs also return source and result sizes, savings, chosen format, content class, perceptual guard outcome, search effort, and the final decision reason in optimization.
queuedvalidatingprocessingsucceededfailedcancel_requestedcanceledexpiredcurl https://api.pixmender.com/v1/jobs/job_01... \
--header "Authorization: Bearer $PIXMENDER_API_KEY"{
"id": "job_01...",
"status": "succeeded",
"credits_charged": 1.5,
"credits_refunded": 0,
"input_byte_size": 2471936,
"input_content_type": "image/jpeg",
"result_asset_id": "ast_result_01...",
"result_filename": "photo.avif",
"result_byte_size": 812640,
"result_content_type": "image/avif",
"optimization": {
"source_bytes": 2471936,
"result_bytes": 812640,
"savings_percent": 67.125,
"format": "avif",
"quality": 57,
"similarity": 0.99124,
"guard_passed": true,
"attempts": 12,
"candidate_formats": [
"avif",
"webp",
"jpeg"
],
"content_class": "photo",
"deadline_reached": false,
"original_preserved": false,
"reason": "optimized",
"encode_ms": 2840
},
"error": null
}Download the result
The API returns a temporary signed URL valid for 10 minutes. The processed file itself remains available for 24 hours unless deleted earlier.
curl 'https://api.pixmender.com/v1/assets/ast_result_01.../download?disposition=attachment' \
--header "Authorization: Bearer $PIXMENDER_API_KEY"
# Response: { "url": "https://storage.pixmender.com/...", "expires_in": 600 }
curl --location "https://storage.pixmender.com/..." --output photo.webpDelete originals and results
After downloading all required files, call DELETE /v1/assets/{assetId}for each original and result. Deletion is free and requires images:write. It removes that image and its cached previews. Other results and job history are retained. Repeating the request returns the same successful response.
An asset used by a queued or running job returns 409; wait for completion or cancellation. A 503 means access is already blocked but storage cleanup is pending; retry the request. Unknown assets and assets owned by another user return 404.
curl -X DELETE "https://api.pixmender.com/v1/assets/ast_example" -H "Authorization: Bearer $PIXMENDER_API_KEY"{"id":"ast_example","deleted":true}await client.deleteAsset(assetId); // TypeScript
client.delete_asset(asset_id) # Python
$client->deleteAsset($assetId); // PHPOperations reference
A pipeline contains 1–5 operations. If used, optimizemust be last. upscale must be the final processing operation and may only be followed by optimize.
For standalone resize, send an empty operations array and specify output_size.
remove_watermarkRemove visible watermarksJob: rights_attested: trueremove_textRemove captions and lettering—remove_logoRemove brand marks—remove_objectRemove a described objectprompt is requiredrestoreRepair damage and degradation—denoiseReduce noise and compression artifacts—deblurImprove blurred details—face_enhanceRefine portrait details—upscaleIncrease resolutionscale: 2 | 4; final processing stepoptimizeContent-aware perceptual compression; AUTO selects the best candidatemust be lastsource keeps the verified input formatReceive signed webhooks
Create an endpoint with the webhooks:manage scope or manage it from Webhooks. PixMender sends job.succeeded, job.failed, and job.canceled. Return any 2xx response within 10 seconds; failed deliveries retry with exponential backoff.
curl --request POST https://api.pixmender.com/v1/webhooks \
--header "Authorization: Bearer $PIXMENDER_API_KEY" \
--header "Content-Type: application/json" \
--data '{"url":"https://example.com/pixmender","events":["job.succeeded","job.failed"]}'webhook-id.webhook-timestamp.raw-body. Store the whsec_… secret shown at creation; it is displayed once.import { createHmac, timingSafeEqual } from "node:crypto";
const eventId = request.headers.get('webhook-id');
const timestamp = request.headers.get('webhook-timestamp');
const signatureHeader = request.headers.get('webhook-signature');
if (!eventId || !timestamp || !signatureHeader?.startsWith('v1=')) {
throw new Error('Missing webhook signature headers');
}
const received = signatureHeader.slice(3);
if (!/^[a-f0-9]{64}$/i.test(received)) {
throw new Error('Invalid webhook signature format');
}
const rawBody = await request.text();
const timestampNumber = Number(timestamp);
const age = Math.abs(Date.now() / 1000 - timestampNumber);
if (!Number.isFinite(timestampNumber) || age > 300) {
throw new Error('Stale webhook');
}
const secret = process.env.PIXMENDER_WEBHOOK_SECRET;
if (!secret) throw new Error('Webhook secret is not configured');
const expected = createHmac('sha256', secret)
.update(`${eventId}.${timestamp}.${rawBody}`)
.digest('hex');
const receivedBytes = Buffer.from(received, 'hex');
const expectedBytes = Buffer.from(expected, 'hex');
if (receivedBytes.length !== expectedBytes.length ||
!timingSafeEqual(receivedBytes, expectedBytes)) {
throw new Error('Invalid webhook signature');
}Delivery headers are webhook-id, webhook-timestamp, and webhook-signature: v1=…. Deduplicate by webhook ID.
Use a server-side SDK
The server-side SDKs combine signed upload, job creation, polling, and result download into one method. Keep the API key out of browser code.
import { PixMender } from "@pixmender/sdk";
const client = new PixMender({
apiKey: process.env.PIXMENDER_API_KEY!,
});
const result = await client.processImage({
file: "./photo.jpg",
// Optimize must be the final operation.
operations: [{ type: "denoise" }, { type: "upscale", scale: 4 }, { type: "optimize" }],
idempotencyKey: "workflow-run-123",
outputFormat: "webp",
compressionPreset: "balanced",
});
await result.downloadTo("./photo-enhanced.webp");import os
from pixmender import PixMender
client = PixMender(api_key=os.environ["PIXMENDER_API_KEY"])
result = client.process_image(
file="photo.jpg",
operations=[{"type": "restore"}, {"type": "optimize"}],
idempotency_key="workflow-run-123",
output_format="webp",
compression_preset="balanced",
)
result.download_to("photo-enhanced.webp")use PixMender\PixMender;
$client = new PixMender($_ENV['PIXMENDER_API_KEY']);
$result = $client->processImage(
__DIR__ . '/photo.jpg',
[['type' => 'restore'], ['type' => 'optimize']],
'workflow-run-123',
outputFormat: 'webp',
compressionPreset: 'balanced',
);
$result->downloadTo(__DIR__ . '/photo-enhanced.webp');# TypeScript
npm install https://pixmender.com/downloads/pixmender-sdk-0.1.0.tgz
# Python
pip install https://pixmender.com/downloads/pixmender-0.1.0-py3-none-any.whl
# PHP / Composer
composer config repositories.pixmender composer https://pixmender.com/downloads/composer
composer require pixmender/pixmender-php:^0.1Connect automation tools
The n8n and Make integrations accept a binary image, execute the full asynchronous workflow, and return the processed image as a binary output for the next step in the scenario.
# Run in the custom-nodes directory of a self-hosted n8n instance
npm install https://pixmender.com/downloads/n8n-nodes-pixmender-0.1.0.tgzMigrate from Tinify
Existing Tinify HTTP integrations can keep the optimize request shape. Point them to https://api.pixmender.com, keep Basic Auth, use api as the username and your PixMender API key as the password.
curl https://api.pixmender.com/shrink \
--user "api:$PIXMENDER_API_KEY" \
--data-binary "@photo.png" \
--dump-header /dev/stdoutA successful request returns 201 Created. The JSON body includes size, type, dimensions, compression ratio and the public download address inoutput.url. The same URL is returned in the Location header and works without an API key until the result expires.
{
"input": {
"size": 98868,
"type": "image/jpeg"
},
"output": {
"size": 88373,
"type": "image/jpeg",
"width": 1280,
"height": 853,
"ratio": 0.8938,
"url": "https://api.pixmender.com/output/job_..."
}
}curl https://api.pixmender.com/output/job_... \
--output "photo-optimized.png"curl https://api.pixmender.com/shrink \
--user "api:$PIXMENDER_API_KEY" \
--header "Content-Type: application/json" \
--data '{"source":{"url":"https://example.com/photo.jpg"}}'/shrink uses the production same-format optimizer and its existing quality guard; it does not expose the richer mode and format choices of the main processing API. Each successful optimization costs 0.1 credit.