SimplyPNG/Documentation

API Reference

Complete reference for all SimplyPNG API endpoints.

Base URL

https://api.simplypng.app/api/v1

Headers

All requests require the following headers:

HeaderValueRequired
AuthorizationBearer YOUR_API_KEYYes
Content-Typeapplication/jsonYes (for POST)
Idempotency-KeyUnique string (max 255 chars)No
POST/jobs
Create Job
Create a new background removal job. Returns immediately with job ID.

Request Body

ParameterTypeRequiredDescription
imagestringYesImage URL (recommended) or base64 data URL. Supported formats: JPEG, PNG, WebP, HEIC/HEIF.
output_modestringNodownload_url (default) or base64_json
idempotency_keystringNoUnique key to prevent duplicate jobs. Also accepted via Idempotency-Key header (max 255 chars).
options.hd_modebooleanNoEnable HD processing (2 credits per image). Preserves images up to 4096px. Without HD mode, images are optimized to 2500px max dimension for faster processing.
options.max_dimensionnumberNoOverride the default input resize target (512–4096). Default: 2500px (standard) or 4096px (HD mode). Use this when you need a specific output resolution.
options.output_typestringNooriginal (default) or centered. Centered places the subject on a canvas.
options.backgroundstringNotransparent (default), white, or custom. Note: centered + transparent auto-corrects to white.
options.background_colorstringConditionalRequired when background is custom. Hex color with # prefix (e.g., #FF5733 or #F00).
options.output_formatstringNopng (default) or jpg. JPEG does not support transparency; if jpg + transparent, background auto-corrects to white.
options.jpeg_qualitynumberNoJPEG quality, 50–100 (default: 85). Only applies when output_format is jpg.
options.canvas_size_presetstringNoOnly for centered output. Values: small (1000×1000), medium (1500×1500), standard (2000×2000, default), optimal (2500×2500), large (3000×3000), maximum (4096×4096), custom.
options.canvas_widthnumberNoCustom canvas width in pixels. Only when canvas_size_preset is custom.
options.canvas_heightnumberNoCustom canvas height in pixels. Only when canvas_size_preset is custom.
options.fill_ratio_presetstringNoOnly for centered output. Values: spacious (80%), standard (85%, default), tight (90%).
options.fill_rationumberNoCustom fill ratio (0.5–0.95). Overrides fill_ratio_preset. Only for centered output.

Example Request (URL Input - Recommended)

curl -X POST https://api.simplypng.app/api/v1/jobs \
  -H "Authorization: Bearer sp_test_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "image": "https://your-storage.com/photo.jpg",
    "output_mode": "download_url",
    "idempotency_key": "unique-request-id-123"
  }'

URL input is recommended for large images (up to 30MB). For base64, use data:image/png;base64,... format (max ~4.5MB payload). Images above ~3000px should use URL input to avoid payload limits.

Input Image Optimization

By default, input images larger than 2500px are resized to 2500px (longest edge) before processing. This exceeds all major e-commerce marketplace requirements (Amazon 2000px, Shopify 2048px, Etsy 2000px) and ensures faster processing. To preserve full resolution up to 4096px, enable hd_mode: true (2 credits). You can also set a specific target with max_dimension (512–4096).

Supported Input Formats

JPEG / JPGPNGWebPHEIC / HEIF

GIF, BMP, TIFF, and SVG are not supported. Unsupported formats return a 400 UNSUPPORTED_FORMAT error.

Response (201 Created)

{
  "job": {
    "id": "a6b60315-e26c-4a8c-8100-82c3cc9cfcb9",
    "status": "running",
    "output_mode": "download_url",
    "created_at": "2025-12-22T09:22:44.393Z"
  },
  "message": "Job created successfully. Poll GET /api/v1/jobs/{id} for status.",
  "request_id": "req_1703260800000_a1b2c3d4e5f6"
}
GET/jobs/:id
Get Job Status
Get the status and result of a background removal job.

Path Parameters

ParameterTypeDescription
idstringJob ID returned from create job

Example Request

curl https://api.simplypng.app/api/v1/jobs/a6b60315-e26c-4a8c-8100-82c3cc9cfcb9 \
  -H "Authorization: Bearer sp_test_YOUR_API_KEY"

Response - Running

{
  "job": {
    "id": "a6b60315-e26c-4a8c-8100-82c3cc9cfcb9",
    "status": "running",
    "output_mode": "download_url",
    "credits_charged": 0,
    "created_at": "2025-12-22T09:22:44.393Z"
  },
  "request_id": "req_1703260801000_b2c3d4e5f6a7"
}

Response - Succeeded (download_url)

{
  "job": {
    "id": "a6b60315-e26c-4a8c-8100-82c3cc9cfcb9",
    "status": "succeeded",
    "output_mode": "download_url",
    "credits_charged": 1,
    "created_at": "2025-12-22T09:22:44.393Z",
    "result": {
      "type": "url",
      "url": "https://storage.simplypng.app/output/abc123.png",
      "thumbnail_url": "https://storage.simplypng.app/output/abc123_thumb.png",
      "expires_at": "2025-12-22T10:22:44.393Z"
    }
  },
  "request_id": "req_1703260802000_c3d4e5f6a7b8"
}

Response - Succeeded (base64_json)

{
  "job": {
    "id": "a6b60315-e26c-4a8c-8100-82c3cc9cfcb9",
    "status": "succeeded",
    "output_mode": "base64_json",
    "credits_charged": 1,
    "created_at": "2025-12-22T09:22:44.393Z",
    "result": {
      "type": "base64",
      "data": "iVBORw0KGgoAAAANSUhEUgAA...",
      "format": "png"
    }
  },
  "request_id": "req_1703260802000_c3d4e5f6a7b8"
}

Response - Failed

{
  "job": {
    "id": "a6b60315-e26c-4a8c-8100-82c3cc9cfcb9",
    "status": "failed",
    "output_mode": "download_url",
    "credits_charged": 0,
    "created_at": "2025-12-22T09:22:44.393Z",
    "error": {
      "code": "PROCESSING_FAILED",
      "message": "Image processing failed"
    }
  },
  "request_id": "req_1703260802000_c3d4e5f6a7b8"
}
POST/jobs/batch
Create Batch Job
Process multiple images in a single request. Returns immediately with batch ID and individual job IDs.

Request Body

ParameterTypeRequiredDescription
imagesarrayYesArray of image objects. Each must have url or base64. Optional id for client correlation.
output_modestringNodownload_url (default) or base64_json
webhook_urlstringNoHTTPS URL to receive a POST notification when the batch completes. A webhook_secret is returned in the response for signature verification.
idempotency_keystringNoUnique key to prevent duplicate batches (max 255 chars).
optionsobjectNoSame processing options as single job (hd_mode, output_type, background, etc.). Applied to all images.

Batch Limits by Plan

PlanMax ImagesMax Total Size
PayG / Basic10250 MB
Plus25500 MB
Pro / Creator / Studio50Up to 10 GB

Example Request

curl -X POST https://api.simplypng.app/api/v1/jobs/batch \
  -H "Authorization: Bearer sp_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "images": [
      {"url": "https://your-storage.com/photo1.jpg", "id": "product-001"},
      {"url": "https://your-storage.com/photo2.jpg", "id": "product-002"}
    ],
    "options": {
      "output_type": "centered",
      "background": "white",
      "output_format": "jpg"
    }
  }'

Response (202 Accepted)

{
  "batch": {
    "id": "batch_abc123",
    "status": "processing",
    "total_count": 2
  },
  "jobs": [
    {"id": "job_1", "status": "running", "index": 0},
    {"id": "job_2", "status": "running", "index": 1}
  ],
  "request_id": "req_..."
}

Webhook Notifications

When you provide a webhook_url, the response includes a webhook_secret in the batch object. Store this secret securely — it is only returned once. When the batch completes, SimplyPNG sends a POST request to your URL with HMAC-SHA256 signature verification.

Webhook Headers

HeaderDescription
X-SimplyPNG-Signaturesha256=<hmac_hex>
X-SimplyPNG-TimestampUnix timestamp (seconds) — reject if older than 5 minutes

Webhook Payload

{
  "event": "batch.completed",
  "batch_id": "batch_abc123",
  "status": "completed",
  "total": 50,
  "succeeded": 48,
  "failed": 2,
  "created_at": "2026-02-16T...",
  "completed_at": "2026-02-16T..."
}

Event types: batch.completed, batch.partial, batch.failed

Verifying Signatures (Python)

import hmac, hashlib, time

def verify_webhook(raw_body: bytes, signature: str, timestamp: str, secret: str) -> bool:
    # Reject if older than 5 minutes (replay protection)
    if abs(time.time() - int(timestamp)) > 300:
        return False
    signed_payload = f"{timestamp}.{raw_body.decode()}"
    expected = "sha256=" + hmac.new(
        secret.encode(), signed_payload.encode(), hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)
GET/jobs/batch/:id
Get Batch Status
Get the status of a batch job including individual results.

Response — Completed

{
  "batch": {
    "id": "batch_abc123",
    "status": "completed",
    "total_count": 2
  },
  "results": [
    {
      "index": 0,
      "job_id": "job_1",
      "client_id": "product-001",
      "status": "succeeded",
      "credits_charged": 1,
      "result": {
        "url": "https://storage.simplypng.app/...",
        "thumbnail_url": "https://storage.simplypng.app/...",
        "expires_at": "2026-02-09T..."
      }
    }
  ],
  "timing": {
    "total_seconds": 16,
    "avg_per_image": 8
  },
  "request_id": "req_..."
}

Results may be returned in completion order, not submission order. Use client_id to correlate results with your input images.

Processing Large Volumes (1,000+ Images)
The batch endpoint accepts up to 50 images per request. To process larger volumes, split your images into batches and submit them with controlled concurrency.

Workflow

  1. Split your images into batches of up to 50
  2. Submit batches with controlled concurrency (3–5 in parallel)
  3. Poll each batch status via GET /jobs/batch/:id
  4. Download results as each batch completes (1-hour download window)

Time Estimates (1,000 images, standard mode)

StrategyBatchesEst. Wall-Clock TimeCredits
Sequential (1 at a time)20~28 min1,000
3 concurrent20~10 min1,000
5 concurrent20~6 min1,000

Estimates assume ~1.4s/image processing + 15s cold start per batch. Actual times depend on image size and GPU availability. HD mode (~1.7s/image) uses 2 credits per image.

Python Example

import requests
import time
import concurrent.futures

API_KEY = "sp_live_YOUR_KEY"
BASE = "https://api.simplypng.app/api/v1"
BATCH_SIZE = 50
MAX_CONCURRENT = 3

def submit_batch(image_urls):
    """Submit a batch of up to 50 image URLs."""
    resp = requests.post(
        f"{BASE}/jobs/batch",
        headers={
            "Authorization": f"Bearer {API_KEY}",
            "Content-Type": "application/json",
        },
        json={
            "images": [{"url": u} for u in image_urls],
            "output_mode": "download_url",
        },
    )
    resp.raise_for_status()
    return resp.json()["batch"]["id"]

def poll_batch(batch_id):
    """Poll until batch completes."""
    while True:
        resp = requests.get(
            f"{BASE}/jobs/batch/{batch_id}",
            headers={"Authorization": f"Bearer {API_KEY}"},
        )
        data = resp.json()
        status = data["batch"]["status"]
        if status in ("completed", "partial", "failed"):
            return data
        time.sleep(5)

# 1. Split into batches of 50
all_urls = ["https://example.com/img1.jpg", ...]  # your 1000 URLs
batches = [
    all_urls[i : i + BATCH_SIZE]
    for i in range(0, len(all_urls), BATCH_SIZE)
]

# 2. Submit with controlled concurrency
with concurrent.futures.ThreadPoolExecutor(
    max_workers=MAX_CONCURRENT
) as pool:
    batch_ids = list(pool.map(submit_batch, batches))

print(f"Submitted {len(batch_ids)} batches")

# 3. Poll and download results
for batch_id in batch_ids:
    result = poll_batch(batch_id)
    succeeded = result["batch"]["succeeded"]
    total = result["batch"]["total"]
    print(f"Batch {batch_id}: {succeeded}/{total} succeeded")
    for r in result["results"]:
        if r["status"] == "succeeded":
            url = r["result"]["url"]
            # Download: requests.get(url, headers=...)

Node.js Example

const API_KEY = "sp_live_YOUR_KEY";
const BASE = "https://api.simplypng.app/api/v1";
const BATCH_SIZE = 50;
const MAX_CONCURRENT = 3;

async function submitBatch(imageUrls) {
  const resp = await fetch(`${BASE}/jobs/batch`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      images: imageUrls.map((url) => ({ url })),
      output_mode: "download_url",
    }),
  });
  const data = await resp.json();
  return data.batch.id;
}

async function pollBatch(batchId) {
  while (true) {
    const resp = await fetch(`${BASE}/jobs/batch/${batchId}`, {
      headers: { Authorization: `Bearer ${API_KEY}` },
    });
    const data = await resp.json();
    if (["completed", "partial", "failed"].includes(data.batch.status)) {
      return data;
    }
    await new Promise((r) => setTimeout(r, 5000));
  }
}

async function processAll(allUrls) {
  // 1. Split into batches of 50
  const batches = [];
  for (let i = 0; i < allUrls.length; i += BATCH_SIZE) {
    batches.push(allUrls.slice(i, i + BATCH_SIZE));
  }

  // 2. Submit with controlled concurrency
  const batchIds = [];
  for (let i = 0; i < batches.length; i += MAX_CONCURRENT) {
    const chunk = batches.slice(i, i + MAX_CONCURRENT);
    const ids = await Promise.all(chunk.map(submitBatch));
    batchIds.push(...ids);
  }

  console.log(`Submitted ${batchIds.length} batches`);

  // 3. Poll and collect results
  for (const id of batchIds) {
    const result = await pollBatch(id);
    const { succeeded, total } = result.batch;
    console.log(`Batch ${id}: ${succeeded}/${total} succeeded`);
  }
}

Tips

  • Use idempotency_key per batch to safely retry on network failures
  • Download results promptly — output URLs expire after 1 hour
  • Use URL input (not base64) for large images to avoid payload limits
  • For HD mode, set options.hd_mode: true (2 credits per image)
  • Monitor your credit balance via GET /credits/balance before large runs

Job Statuses

StatusDescription
pendingJob is queued and waiting to be processed
runningJob is currently being processed
succeededJob completed successfully, result available
failedJob failed, check error for details

Error Response Format

All errors follow this format:

{
  "error": {
    "type": "error_type",
    "code": "ERROR_CODE",
    "message": "Human-readable error message",
    "param": "field_name"  // optional, for validation errors
  },
  "request_id": "req_1703260800000_a1b2c3d4e5f6"
}

Error Types

TypeHTTP StatusDescription
validation_error400Invalid request parameters
authentication_error401Missing or invalid API key
billing_error402/403Insufficient credits or inactive plan
not_found_error404Resource not found
conflict409Duplicate idempotency key in progress
rate_limit_error429Too many requests
internal_error500Server error, retry later

Validation Error Codes

When request parameters are invalid, the API returns 400 with a specific error code:

CodeTrigger
INVALID_OUTPUT_TYPEInvalid output_type value (valid: original, centered)
INVALID_BACKGROUNDInvalid background value (valid: transparent, white, custom)
INVALID_BACKGROUND_COLORInvalid hex color format (must be #RRGGBB or #RGB)
MISSING_BACKGROUND_COLORbackground is custom but background_color is missing
INVALID_OUTPUT_FORMATInvalid output_format value (valid: png, jpg)
INVALID_CANVAS_PRESETInvalid canvas_size_preset value
INVALID_OUTPUT_MODEInvalid output_mode value (valid: download_url, base64_json)
INVALID_WEBHOOK_URLInvalid or non-HTTPS webhook_url (batch endpoint only)
SSRF_BLOCKEDImage URL points to a private or internal address
UNSUPPORTED_FORMATImage format not supported (GIF, BMP, TIFF, SVG)

Auto-Correction Behaviors

Some conflicting option combinations are automatically resolved instead of rejected:

ConflictResolution
centered + transparentBackground changed to white
jpg + transparentBackground changed to white (JPEG format preserved)

Idempotency

To prevent duplicate jobs from network retries, include an idempotency key in your request. If a job with the same key already exists, the API will return the existing job instead of creating a new one. Keys are scoped per owner (user or organization) and can be up to 255 characters long.

You can provide the key via either method (header takes priority):

  • HTTP Header (recommended): Idempotency-Key: your-unique-key
  • Request body: "idempotency_key": "your-unique-key"
curl -X POST https://api.simplypng.app/api/v1/jobs \
  -H "Authorization: Bearer sp_live_YOUR_KEY" \
  -H "Idempotency-Key: user-123-image-456-v1" \
  -H "Content-Type: application/json" \
  -d '{"image": "https://your-storage.com/photo.jpg"}'

The response will include "idempotent": true if returning an existing job. If another request with the same key is currently being processed, you will receive a 409 Conflict response.

Request IDs

Every API response includes a request_id. Include this ID when contacting support about specific requests. Request IDs are also returned in the X-Request-ID header.