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. One credit is reserved for each operation and automatically returned if processing fails. 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.
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"
}
],
"output_format": "webp"
}'{
"id": "job_01...",
"status": "queued",
"credits_reserved": 2,
"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.
queuedvalidatingprocessingsucceededfailedcancel_requestedcanceledexpiredcurl https://api.pixmender.com/v1/jobs/job_01... \
--header "Authorization: Bearer $PIXMENDER_API_KEY"{
"id": "job_01...",
"status": "succeeded",
"credits_charged": 2,
"credits_refunded": 0,
"result_asset_id": "ast_result_01...",
"result_filename": "photo.webp",
"result_byte_size": 1843200,
"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.
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.webpOperations reference
A pipeline contains 1–5 operations. If used, upscalemust be the final operation.
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; must be lastReceive 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",
operations: [{ type: "denoise" }, { type: "upscale", scale: 4 }],
idempotencyKey: "workflow-run-123",
outputFormat: "webp",
});
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"}],
idempotency_key="workflow-run-123",
output_format="webp",
)
result.download_to("photo-enhanced.webp")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');# 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.tgz