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

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

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

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",
  operations: [{ type: "denoise" }, { type: "upscale", scale: 4 }],
  idempotencyKey: "workflow-run-123",
  outputFormat: "webp",
});
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"}],
    idempotency_key="workflow-run-123",
    output_format="webp",
)
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']],
    'workflow-run-123',
    outputFormat: 'webp',
);
$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

Switch from TinyPNG

The optimize-only compatibility API follows the Tinify HTTP flow. Keep Basic Auth, use api as the username and your PixMender API key as the password, then point requests to https://api.pixmender.com.

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 the download URL in output.url; the same URL is also returned in the Location header. The unguessable URL can be downloaded directly 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 25 megapixels. Resize, convert, preserve metadata, and store are not available on this endpoint. Each successful optimization costs 0.1 credit.