Image Processing API

Upload an image, run up to five ordered operations, and download the result through one asynchronous workflow.

Base URL
https://api.pixmender.com
Interactive docs
Swagger UI
1Create key
2Request upload
3Upload image
4Create job
5Check status
6Download
01

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.

Keep it server-sideNever expose an img_live_… key in browser code or a public repository.
Terminal
export PIXMENDER_API_KEY="img_live_your_key"
02

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.

Request
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"}'
Response
{
  "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.

03

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.

Request
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"
04

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.

Request
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"
  }'
Optional output sizeSet 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 presetThe production API retains 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.

202 Accepted
{
  "id": "job_01...",
  "status": "queued",
  "credits_reserved": 1.5,
  "input_asset_id": "ast_01...",
  "result_asset_id": null
}
05

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_requestedcanceledexpired
Request
curl https://api.pixmender.com/v1/jobs/job_01... \
  --header "Authorization: Bearer $PIXMENDER_API_KEY"
Succeeded response
{
  "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
}
06

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.

Request
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.webp

Delete 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.

Request
curl -X DELETE "https://api.pixmender.com/v1/assets/ast_example" -H "Authorization: Bearer $PIXMENDER_API_KEY"
Response
{"id":"ast_example","deleted":true}
SDK methods
await client.deleteAsset(assetId); // TypeScript
client.delete_asset(asset_id)       # Python
$client->deleteAsset($assetId);     // PHP
07

Operations 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.

TypePurposeExtra fields
remove_watermarkRemove visible watermarksJob: rights_attested: true
remove_textRemove captions and lettering—
remove_logoRemove brand marks—
remove_objectRemove a described objectprompt is required
restoreRepair damage and degradation—
denoiseReduce noise and compression artifacts—
deblurImprove blurred details—
face_enhanceRefine portrait details—
upscaleIncrease resolutionscale: 2 | 4; final processing step
optimizeContent-aware perceptual compression; AUTO selects the best candidatemust be last
Output formatssource · png · jpeg · webp · avif; source keeps the verified input format
Upscale modelsrealesrgan-x4plus · realesrgan-x4plus-anime · realesr-animevideov3
08

Receive 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.

Create endpoint
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"]}'
Verify the raw request bodySignatures use HMAC-SHA256 over webhook-id.webhook-timestamp.raw-body. Store the whsec_… secret shown at creation; it is displayed once.
Node.js signature verification
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.

09

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.

TypeScript · @pixmender/sdk
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");
Python · pixmender
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")
PHP · pixmender/pixmender-php
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');
Direct downloadsVersion 0.1.0 · hosted by PixMender
SHA-256 checksums
TypeScriptNode.js 18+
@pixmender/sdk
PythonPython 3.10+
pixmender
PHPPHP 8.1+
pixmender/pixmender-php
Install directly from pixmender.com
# 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.1
10

Connect 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.

Direct downloadsVersion 0.1.0 · direct source packages
n8nSelf-hosted installation
n8n-nodes-pixmender
MakeDeveloper app components
PixMender Custom App
Install on self-hosted n8n
# Run in the custom-nodes directory of a self-hosted n8n instance
npm install https://pixmender.com/downloads/n8n-nodes-pixmender-0.1.0.tgz
11

Migrate 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.

Optimize a local image
curl https://api.pixmender.com/shrink \
  --user "api:$PIXMENDER_API_KEY" \
  --data-binary "@photo.png" \
  --dump-header /dev/stdout

A 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.

201 Created
{
  "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_..."
  }
}
Download without authorization
curl https://api.pixmender.com/output/job_... \
  --output "photo-optimized.png"
Optimize an image URL
curl https://api.pixmender.com/shrink \
  --user "api:$PIXMENDER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"source":{"url":"https://example.com/photo.jpg"}}'
Compatibility scopeSupports optimize for PNG, JPEG, WebP, and AVIF images up to 5 MB and 60 megapixels. Resize, convert, preserve metadata, and store are not available on this endpoint. /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.