Free Image Compression, Conversion & Async API

Upload files, process batches, fetch remote URLs, send Base64 or raw image bytes, resize images and track asynchronous jobs without keeping a long browser request open.

HTTP 202 Async JobsJPG / PNG / WebP / AVIFBatch ZIPBearer Job TokenNo framework

Compress and convert images online

After submission, the page shows queue position, processing stage and progress, then downloads the result through a protected endpoint.

Why asynchronous image conversion?

Return immediately

A successful submission returns HTTP 202, a job ID and an access token without keeping a long request open.

Queue larger workloads

Jobs can wait for an available worker, which suits slower servers, batches and later result retrieval.

Recoverable client flow

Store the job ID and token to continue polling and downloading during the retention period, even after the page closes.

Async API: convert in three steps

Submit a job
Send multipart, JSON or raw image bytes to action=submit or action=jobs.
Poll status
Store job.id and access_token, then poll with Authorization: Bearer.
Download the result
Download after completed or partially_completed; single jobs return an image and batches return ZIP.

1. Submit a file

curl -X POST 'https://www.livetops.com/tools/img-api.php?action=submit&lang=en' \
  -F 'image=@photo.jpg' \
  -F 'format=webp' \
  -F 'quality=82' \
  -F 'max_side=1600'

The response is HTTP 202. access_token is returned only in the creation response; store it safely and never place it in public logs.

2. Poll progress

curl 'https://www.livetops.com/tools/img-api.php?action=status&id=j_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&lang=en' \
  -H 'Authorization: Bearer YOUR_JOB_ACCESS_TOKEN'

3. Download the result

curl -L 'https://www.livetops.com/tools/img-api.php?action=download&id=j_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
  -H 'Authorization: Bearer YOUR_JOB_ACCESS_TOKEN' \
  -o result.webp

Input patterns

multipart/form-data

# Use image for one file or images[] for batchescurl -X POST 'https://www.livetops.com/tools/img-api.php?action=submit' \
  -F 'images[]=@a.jpg' \
  -F 'images[]=@b.png' \
  -F 'format=webp'

JSON remote URL

{
  "image_url": "https://example.com/photo.jpg",
  "format": "webp",
  "quality": 82,
  "max_side": 1600
}

Multiple remote URLs

{
  "image_urls": [
    "https://example.com/a.jpg",
    "https://example.com/b.png"
  ],
  "format": "webp",
  "quality": 80
}

Base64 / Data URL

{
  "image_base64": "data:image/png;base64,iVBORw0KGgoAAA...",
  "format": "jpeg",
  "background": "#ffffff"
}

Raw binary request body

curl -X POST 'https://www.livetops.com/tools/img-api.php?action=submit&format=png' \
  -H 'Content-Type: image/jpeg' \
  --data-binary '@photo.jpg'
Use one primary input mode per request. Remote URLs are validated by the service; invalid, unreachable or disallowed resources return a standard error.

Job states and response structure

queued → processing → completed,or terminate as partially_completedfailedcancelledexpired

HTTP 202 creation response

{
  "success": true,
  "request_id": "r_...",
  "job": {
    "id": "j_...",
    "status": "queued",
    "stage": "waiting",
    "progress": 5,
    "poll_after_ms": 2000,
    "expires_at": "...",
    "links": {"status": "...", "download": "...", "cancel": "..."}
  },
  "access_token": "..."
}

processing response

{
  "success": true,
  "job": {
    "status": "processing",
    "stage": "encoding",
    "progress": 72,
    "queue_position": 0,
    "total_items": 3,
    "completed_items": 2,
    "failed_items": 0,
    "download_ready": false
  }
}

standard error

{
  "success": false,
  "request_id": "r_...",
  "error": {
    "code": "FILE_TOO_LARGE",
    "message": "...",
    "details": {"max_bytes": 25165824}
  }
}

Use error.code and the HTTP status for program logic rather than the localized message. progress represents job-stage progress, not byte-precise encoder progress.

Complete client examples

JavaScript / TypeScript

async function convertImage(file) {
  const form = new FormData();
  form.append('image', file);
  form.append('format', 'webp');
  form.append('quality', '82');

  const createdResponse = await fetch('https://www.livetops.com/tools/img-api.php?action=submit', {
    method: 'POST', body: form
  });
  const created = await createdResponse.json();
  if (!createdResponse.ok) throw new Error(created.error?.code || 'SUBMIT_FAILED');

  const headers = { Authorization: `Bearer ${created.access_token}` };
  let job = created.job;
  while (!['completed','partially_completed','failed','cancelled','expired'].includes(job.status)) {
    await new Promise(r => setTimeout(r, job.poll_after_ms || 2000));
    const response = await fetch(job.links.status, { headers, cache: 'no-store' });
    const payload = await response.json();
    if (!response.ok) throw new Error(payload.error?.code || 'STATUS_FAILED');
    job = payload.job;
  }
  if (!job.download_ready) throw new Error(job.error?.code || job.status);
  const result = await fetch(job.links.download, { headers });
  if (!result.ok) throw new Error('DOWNLOAD_FAILED');
  return await result.blob();
}

Python requests

import time
import requests

with open("photo.jpg", "rb") as image:
    response = requests.post(
        "https://www.livetops.com/tools/img-api.php?action=submit",
        files={"image": ("photo.jpg", image, "image/jpeg")},
        data={"format": "webp", "quality": 82, "max_side": 1600},
        timeout=60,
    )
response.raise_for_status()
created = response.json()
headers = {"Authorization": f"Bearer {created['access_token']}"}
job = created["job"]

while job["status"] not in {"completed", "partially_completed", "failed", "cancelled", "expired"}:
    time.sleep(job.get("poll_after_ms", 2000) / 1000)
    status = requests.get(job["links"]["status"], headers=headers, timeout=30)
    status.raise_for_status()
    job = status.json()["job"]

if not job.get("download_ready"):
    raise RuntimeError(job.get("error", {}).get("code", job["status"]))
result = requests.get(job["links"]["download"], headers=headers, timeout=120)
result.raise_for_status()
open("result.webp", "wb").write(result.content)

PHP cURL

<?php
$ch = curl_init('https://www.livetops.com/tools/img-api.php?action=submit');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => [
        'image' => new CURLFile('/path/photo.jpg', 'image/jpeg', 'photo.jpg'),
        'format' => 'webp',
        'quality' => 82,
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 60,
]);
$created = json_decode(curl_exec($ch), true, 512, JSON_THROW_ON_ERROR);
curl_close($ch);
$job = $created['job'];
$token = $created['access_token'];
// 按 job.poll_after_ms 轮询 job.links.status,完成后携带 Bearer token 下载。

Node.js 18+

import { openAsBlob } from 'node:fs';
const form = new FormData();
form.set('image', await openAsBlob('photo.jpg'));
form.set('format', 'webp');
const created = await fetch('https://www.livetops.com/tools/img-api.php?action=submit', { method: 'POST', body: form }).then(r => r.json());
const headers = { Authorization: `Bearer ${created.access_token}` };

Go

// 使用 mime/multipart 创建 image 字段,POST 到 action=submit。
// 保存响应中的 job.id 和 access_token;后续 GET status/download 时设置:
req.Header.Set("Authorization", "Bearer " + accessToken)

Java 11+

// 使用 HttpClient 发送 multipart 请求创建任务。
// 状态与下载请求:
HttpRequest request = HttpRequest.newBuilder(URI.create(statusUrl))
    .header("Authorization", "Bearer " + accessToken)
    .GET().build();

C# / .NET

using var form = new MultipartFormDataContent();
form.Add(new StreamContent(File.OpenRead("photo.jpg")), "image", "photo.jpg");
form.Add(new StringContent("webp"), "format");
var created = await http.PostAsync("https://www.livetops.com/tools/img-api.php?action=submit", form);
// 查询与下载时设置 AuthenticationHeaderValue("Bearer", accessToken)。

Download filenames and batch ZIPs

Multipart uploads preserve the original basename and replace only the extension. For example, aaa.png converted to WebP downloads as aaa.webp. Internal storage still uses random names.

curl -F "image=@aaa.png" -F "format=webp" "https://www.livetops.com/tools/img-api.php?action=submit"
# 完成后的状态响应:job.download_name = "aaa.webp"
# 下载响应:Content-Disposition: attachment; filename*=UTF-8''aaa.webp

Batch jobs return a ZIP containing original-basename outputs such as aaa.webp and bbb.webp plus manifest.json. Collisions become aaa-2.webp and aaa-3.webp. Map inputs to outputs using items[].original_name, items[].download_name and outputs[].download_name.

# 可选:单图自定义结果名;批量时自定义 ZIP 名称
-F "output_name=project-final"
# 也可使用请求头:X-Output-Filename: project-final

# Base64 / 原始二进制没有天然文件名,可选:
input_name=aaa.png
# 或请求头:X-Input-Filename: aaa.png

Main processing parameters

ParameterType / valuesDefault and description
formatjpeg, png, webp, avifwebp;Check capabilities for actual support.
output_namestring, optionalCustom download basename. A single result becomes output_name.target-format; for a batch it names the ZIP while files inside retain original basenames. X-Output-Filename is also accepted.
qualityinteger 1–10082;Used for JPEG, WebP and AVIF.
png_levelinteger 0–96;PNG lossless compression level.
width, heightintegerOne dimension preserves aspect ratio; two dimensions use fit.
max_sideintegerLimits the longest edge when width/height are omitted.
fitcontain, cover, crop, stretchcontain;Controls target-canvas fitting.
no_upscalebooleantrue;Prevents enlarging small images.
max_size_kbinteger 1–10240Attempts a maximum size for JPEG, WebP and AVIF; exact bytes are not guaranteed.
background#RRGGBB#ffffff;JPEG transparency or contain padding background.
auto_orientbooleantrue;Corrects JPEG orientation from EXIF.
rotate90, 180, 270, -90Clockwise rotation.
flip_h, flip_vbooleanHorizontal or vertical flip.
grayscalebooleanGrayscale conversion.

Endpoints, HTTP statuses and error handling

PurposeMethod and endpointNotes
Async submitPOST ?action=submit / ?action=jobsHTTP 202
Job statusGET ?action=status&id=... / ?action=job&id=...Bearer token
Download resultGET|HEAD ?action=download&id=...Bearer token
Cancel jobPOST ?action=cancel&id=... / DELETE ?action=job&id=...Bearer token
QuotaGET ?action=quotaReturns current client quotas and whether the service is accepting new jobs.
CapabilitiesGET ?action=capabilitiesReturns formats, public features, defaults and developer-relevant limits.
Sync compatibility codePOST ?action=convert / ?action=batchCurrently disabled for direct public execution; Prefer: respond-async uses the async flow.

200 query/download success; 202 job accepted; 400 bad request; 403 credential/input rejected; 409 result not ready; 410 result expired; 413 size/pixel limit; 415 unsupported format; 429 rate/image quota; 503 temporary service-capacity or processing unavailability.

For 429 and 503, honor Retry-After. Poll according to poll_after_ms. Do not automatically retry 400, 403, 413 or 415; use bounded 1, 2, 4 and 8 second backoff for transient network failures.

SERVICE_CAPACITY_REACHED · DOWNLOAD_TEMPORARILY_UNAVAILABLE · TOO_MANY_PENDING_JOBS · QUEUE_BUSY · FILE_TOO_LARGE · INVALID_IMAGE · UNSUPPORTED_OUTPUT_FORMAT · RESULT_EXPIRED

Service limits, long queues and retention

Public request limits

Ordinary requests, image processing and downloads use separate fair-use limits. Query capabilities and quota for the current public values.

Capacity protection

New jobs may be paused when the service is busy or current public capacity is unavailable. Read the standard error object and Retry-After instead of retrying rapidly.

Return later for results

Job credentials can be used to return later. Completed results are currently retained for about 7 days; rely on expires_at and capabilities for the current value.

Queue position is informational. Image dimensions, output formats and remote speeds differ, so completion order and duration can change. Persist job credentials instead of relying on an open browser page.

Privacy and usage notes

Temporary processing

Files are used only to complete conversion. Inputs are removed after processing by default and results expire after a limited retention period.

Job credentials

A job ID is not the full credential. Status, cancellation and download also require the access token returned at creation.

Content and resource limits

The service checks image content, dimensions, pixels and resource use. Invalid or excessive requests return standard errors.

Frequently asked questions

Is an API token required?

A public API key is not required by default. Each asynchronous job receives its own random access token for status, cancellation and download requests.

How does asynchronous image processing work?

The submit endpoint returns HTTP 202, a job ID and an access token immediately. Poll the status endpoint and download the protected result when processing finishes.

Are images stored permanently?

No. Uploads are used only for processing, and results are removed after a limited retention period. Check capabilities for the current retention value.

Which image formats are supported?

Input support depends on the server GD build and commonly includes JPG, PNG, WebP, GIF, BMP and AVIF. Output supports JPG, PNG, WebP and AVIF when available.

Are batches, remote URLs and Base64 supported?

Yes. The API accepts multipart batches, JSON URL lists, Base64/Data URLs and raw binary request bodies. Batch results are delivered as ZIP files.

When should I use sync or async mode?

Synchronous execution is currently disabled for the public service while compatibility code remains available. Integrations should use asynchronous submission, polling and result download.

What should I do when the service cannot accept a new job?

Read the standard error code and Retry-After header, then retry later. Existing jobs can still be checked through the status endpoint.