Image & SVG
Image Converter API
GET request to /v1/image-converter, returning json in the standard envelope.
/v1/image-converter3 credits / call + 1 per megapixel Free tierAuthentication
Send your API key as a bearer token. Create one free in the dashboard.
Authorization: Bearer $TOOLSXPO_KEYParameters
Send these as a JSON body, as query-string parameters, or as form fields alongside uploaded files (-F file=@photo.jpg, up to 10). A raw image body works too, with the options in the query string. Every request needs one image source: image, imageUrl, an uploaded file, or images for a batch. Fields marked required must be present; everything else has a sensible default.
| Name | Type | Default | Description |
|---|---|---|---|
| image | string | — | The image as base64, or a data: URL (data:image/png;base64,...). JPEG, PNG, WebP, AVIF, GIF or HEIC. |
| imageUrl | string | — | Public http(s) URL to fetch the image from, instead of sending it inline. Max 20 MB. |
| name | string | — | Optional label echoed back in the result, e.g. the file name. |
| images | array of object1–10 | — | Batch mode: up to 10 images, each { image | imageUrl, name? }. Also accepts a list of URLs, or a Source Files step's output. The same options apply to all of them. |
| images[].image | string | — | The image as base64, or a data: URL (data:image/png;base64,...). JPEG, PNG, WebP, AVIF, GIF or HEIC. |
| images[].imageUrl | string | — | Public http(s) URL to fetch the image from, instead of sending it inline. Max 20 MB. |
| images[].name | string | — | Optional label echoed back in the result, e.g. the file name. |
| formatrequired | string | — | Output format: jpeg, png, webp or avif. `jpg` is accepted.One of: jpeg, png, webp, avif |
| quality | integer1–100 | — | 1–100. Defaults: JPEG 82, WebP 80, AVIF 60. Ignored for PNG and lossless output. |
| lossless | boolean | false | Lossless WebP or AVIF. PNG is always lossless; JPEG never is. |
| maxWidth | integer1–12000 | — | Shrink to at most this many pixels wide, keeping the aspect ratio. Never enlarges. |
| maxHeight | integer1–12000 | — | Shrink to at most this many pixels high, keeping the aspect ratio. Never enlarges. |
| background | string | #ffffff | Colour placed behind transparent areas when the output format has no transparency (JPEG). |
| output | string | url | url (default): each image comes back as a download link, kept for your plan's file retention. base64: the image inline in the JSON. Add ?output=binary to the request URL to get the file itself instead of JSON.One of: url, base64 |
Example request
curl "https://api.toolsxpo.com/v1/image-converter?image=iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5%2BAAAAL0lEQVR42mNgAIIojRP%2FpwHxJSBmAAtEAQWA%2BFIUTKAHKADEl3pgAvuAAkB8CYgB9cMhwU8NlV0AAAAASUVORK5CYII%3D&format=webp&quality=80&lossless=false&background=%23ffffff&output=url" \
-H "Authorization: Bearer $TOOLSXPO_KEY"Response
Every endpoint answers with the same envelope: the result in data, what the call cost and your standing in meta. One entry per image in results, totals in summary. By default each image is a download link.
{
"ok": true,
"data": {
"results": [
{
"index": 0, "name": "photo.jpg", "ok": true,
"inputFormat": "jpeg", "format": "webp", "contentType": "image/webp",
"width": 1600, "height": 1067, "inputWidth": 4000, "inputHeight": 2667,
"inputBytes": 3104822, "bytes": 143210, "savedPercent": 95.4,
"quality": 80, "lossless": false, "unchanged": false,
"engine": "images", "warnings": [], "billableMegapixels": 3.84,
"url": "https://ai-cdn.toolsxpo.com/…/image-outputs/9f2c….webp",
"expiresAt": "2026-10-22T14:54:19.772Z"
},
{ "index": 1, "name": "broken.png", "ok": false, "error": "Not a supported image. Send JPEG, PNG, WebP, AVIF, GIF or HEIC." }
],
"summary": {
"total": 2, "succeeded": 1, "failed": 1,
"inputBytes": 3104822, "outputBytes": 143210, "savedPercent": 95.4,
"billableMegapixels": 3.84, "engines": { "images": 1 }
},
"_meter": { "unit": "megapixel", "quantity": 3.84 }
},
"meta": {
"tool": "image-converter", "credits": 5, "baseCredits": 5, "billedFrom": "plan",
"pricing": { "mode": "metered", "meterUnit": "megapixel", "creditsPerUnit": 1 },
"quota": { "limit": 30000, "used": 412, "remaining": 29588, "resetsAt": "2026-11-01T00:00:00.000Z", "scope": "account", "plan": "pro" }
}
}data fields
| Field | Type | Description |
|---|---|---|
| results | array | One entry per input image, in the order sent. |
| results[].index | number | Position of the image in the request (0-based). |
| results[].name | string | null | The `name` you sent, or the uploaded file name. |
| results[].ok | boolean | `false` if this image failed; the others still succeed. A failed image costs nothing. |
| results[].error | string | Why this image failed. Present only when `ok` is false. |
| results[].url | string | Download link for the converted image (default `output: "url"`). Public but unguessable. |
| results[].expiresAt | string (ISO 8601) | When `url` stops working — your plan's file retention (1–20 days). |
| results[].base64 | string | The converted image inline, when you sent `output: "base64"` (or if storing failed — see `warnings`). |
| results[].format | "jpeg" | "png" | "webp" | "avif" | Format of the output. |
| results[].contentType | string | MIME type of the output, e.g. `image/webp`. |
| results[].width | number | Output width in pixels. |
| results[].height | number | Output height in pixels. |
| results[].inputFormat | string | Detected format of the input (jpeg, png, webp, avif, gif, heic…). |
| results[].inputWidth | number | Input width in pixels (after EXIF rotation). |
| results[].inputHeight | number | Input height in pixels. |
| results[].inputBytes | number | Size of the input file. |
| results[].bytes | number | Size of the output file. |
| results[].savedPercent | number | How much smaller the output is (negative if larger, which a format change can cause). |
| results[].quality | number | null | Quality actually used; `null` for PNG and lossless output. With `targetKb`, the quality the search settled on. |
| results[].lossless | boolean | Whether the output is lossless (always true for PNG). |
| results[].unchanged | boolean | Compressor only: no smaller version existed, so the original was returned. |
| results[].engine | "wasm" | "images" | What processed it: our in-Worker codecs, or Cloudflare Images for large photos. |
| results[].warnings | string[] | Anything worth knowing, e.g. transparency filled for JPEG, or only the first frame of a GIF converted. |
| results[].billableMegapixels | number | Units this image added to the charge. |
| summary.total | number | Images in the request. |
| summary.succeeded | number | Images converted. |
| summary.failed | number | Images that failed (not charged). |
| summary.inputBytes | number | Total input size of the successful images. |
| summary.outputBytes | number | Total output size. |
| summary.savedPercent | number | Overall saving. |
| summary.billableMegapixels | number | Total billable units; `meta.credits` = 1 + this, rounded up. |
| summary.engines | object | How many images each engine handled, e.g. `{ "wasm": 2 }`. |
| _meter | object | Billing meter (`{ unit: "megapixel", quantity }`) — the same number as `summary.billableMegapixels`. |
meta fields (every tool)
| Field | Type | Description |
|---|---|---|
| meta.tool | string | The tool id that ran. |
| meta.auth | string | How the call authenticated — `key` for an API key. |
| meta.credits | number | Everything this call cost, in credits (both currencies together). |
| meta.baseCredits | number | The tool's own price — the compute-credit part of `credits`. |
| meta.aiCredits | number | AI credits spent on a model step. Present only when non-zero. |
| meta.billedFrom | string | Which balance paid: the plan allowance or the purchased credit balance. |
| meta.pricing.mode | "flat" | "metered" | Flat price per call, or metered by usage. |
| meta.pricing.meterUnit | string | Metered tools: what is counted (e.g. `megapixel`). |
| meta.pricing.creditsPerUnit | number | Metered tools: credits per unit counted. |
| meta.quota.limit | number | Your plan's allowance for the current period. |
| meta.quota.used | number | Allowance used so far this period. |
| meta.quota.remaining | number | Allowance left this period. |
| meta.quota.resetsAt | string (ISO 8601) | When the allowance resets. |
| meta.quota.creditBalance | number | Purchased credits remaining, used after the allowance. |
| meta.quota.scope | string | What the quota belongs to — `account` (your workspace) for API keys. |
| meta.quota.plan | string | Your plan id. |
Ask for the file itself instead of JSON with ?output=binary on the URL (or an Accept: image/* header): one image comes back as the image, a batch as a ZIP containing every image plus results.json. The headers below carry the numbers. Errors are always JSON.
If every image in a call fails, the call returns 422 and is refunded.
Headers on a binary response
| Field | Type | Description |
|---|---|---|
| Content-Type | header | The image's MIME type, or `application/zip` for a batch. |
| Content-Disposition | header | Suggested file name, e.g. `photo.webp` or `images.zip`. |
| X-ToolsXpo-Credits | header | What the call cost. |
| X-ToolsXpo-Format | header | Single image: output format. |
| X-ToolsXpo-Width / -Height | header | Single image: output dimensions. |
| X-ToolsXpo-Input-Bytes / -Bytes | header | Single image: input and output size. |
| X-ToolsXpo-Saved-Percent | header | Single image: saving. |
| X-ToolsXpo-Quality | header | Single image: quality used (empty for lossless). |
| X-ToolsXpo-Engine | header | Single image: `wasm` or `images`. |
| X-ToolsXpo-Warnings | header | Single image: warnings, separated by ` | `. |
| X-ToolsXpo-Succeeded / -Failed | header | Batch: how many images succeeded and failed. Per-image detail is in `results.json` inside the ZIP. |
Errors
Failures use the same envelope with ok: false and a machine-readable code.
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_input | A parameter failed validation. `details` lists which. |
| 401 | unauthorized | Missing or invalid API key. |
| 402 | quota_exceeded | Out of credits on this workspace. |
| 404 | not_found | No such tool. |
| 429 | rate_limited | Too many requests. Retry after `Retry-After` seconds. |
| 503 | service_unavailable | Temporarily disabled by an administrator. |
How to use the Image Converter API
Convert JPEG, PNG, WebP, AVIF, GIF and HEIC images to WebP, AVIF, JPEG or PNG — one image or up to ten per call, at the quality you choose, optionally resized.
Options
format(required) —webp,avif,jpeg(orjpg),png.quality— 1–100. Defaults: JPEG 82, WebP 80, AVIF 60. Ignored for PNG and lossless output.lossless—truefor exact-pixel WebP or AVIF (larger files).maxWidth/maxHeight— shrink to fit, keeping the aspect ratio. Never enlarges.background— the colour behind transparent areas when converting to JPEG, which has no transparency (default#ffffff). Without it transparent pixels would turn black.
Three ways to send images
Upload files (multipart) — the way most code already sends a file. Repeat -F file=@… for up to 10 images; any other field is an option:
Bashcurl -X POST "https://tools.toolsxpo.com/v1/image-converter?output=binary" \ -H "Authorization: Bearer $image-converterSXPO_KEY" \ -F file=@photo.png -F format=webp -F quality=80 -o photo.webp
A raw body — the image bytes, options in the query string:
Bashcurl -X POST "https://tools.toolsxpo.com/v1/image-converter?format=avif&maxWidth=1200&output=binary" \ -H "Authorization: Bearer $image-converterSXPO_KEY" -H "Content-Type: image/jpeg" \ --data-binary @photo.jpg -o photo.webp
A URL or base64, as JSON — imageUrl (we fetch it, up to 20 MB) or image (base64 or a data: URL). For a batch, send images: [{ imageUrl }, { image }, …] — mix both freely:
JAVASCRIPTconst res = await fetch("https://tools.toolsxpo.com/v1/image-converter", { method: "POST", headers: { Authorization: `Bearer ${process.env.image-converterSXPO_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ images: [{ imageUrl: "https://example.com/a.jpg" }, { imageUrl: "https://example.com/b.png" }], format: "webp", quality: 80 }), }); const { data } = await res.json(); for (const r of data.results) if (r.ok) console.log(r.format, r.bytes, r.savedPercent);
A GET works too: ?imageUrl=…&imageUrl=…&OPTION=… (repeat imageUrl for a batch).
What comes back
- A download link (default):
data.results[]has one entry per image with aurlyou can download, embed or pass on, andexpiresAt— links last for your plan's file retention. Each entry also hasformat,contentType,width,height,inputBytes,bytes,savedPercent,quality,engineandwarnings. Failed images haveok: falseand anerror, and cost nothing.data.summarytotals the call. - Base64 instead: send
output: "base64"and each entry carries the image asbase64rather than a link — nothing is stored. - The file itself: add
?output=binaryto the URL (or sendAccept: image/*). One image comes back as the image; a batch as a ZIP with aresults.jsoninside. Credits, size and format are inX-ToolsXpo-*response headers. Errors are always JSON.
JSON{ "ok": true, "data": { "results": [ { "ok": true, "format": "webp", "width": 1600, "height": 1067, "bytes": 143210, "savedPercent": 82.4, "url": "https://ai-cdn.toolsxpo.com/…/9f2c….webp", "expiresAt": "2026-10-11T09:30:00.000Z" } ], "summary": { "succeeded": 1, "failed": 0 } }, "meta": { "credits": 3 } }
Links are unguessable but public: anyone holding one can open it until it expires. Use output: "base64" or ?output=binary for images that must not be stored.
Cost
A base price per call plus a charge per billable unit — both are shown in the price box on this page and returned in meta.credits with every response. An image's units are 0.2 × input megapixels + output megapixels × (3 for AVIF, else 1), at least 0.25 — so a typical 1–2 MP web image is about 2 units, and shrinking a big photo is cheap because the output is small. If every image in a call fails, the call is refunded.
Limits
- Up to 10 images per call, 20 MB each, 40 MB per call.
- Large photos (roughly over 3–4 megapixels) are processed by Cloudflare Images instead of our in-Worker codecs —
enginetells you which. You do not need to do anything; the cheapest engine that can handle the image is chosen automatically. - AVIF output is limited to about 1.5 MP in the Worker and 1,200 px per side on Cloudflare Images. For a large AVIF, pass
maxWidth. - HEIC and GIF input are accepted (GIF: first frame only). SVG, TIFF and BMP are not accepted by the API — convert those in the browser at toolsxpo.com.
- Metadata (EXIF, GPS) is removed; phone photos are rotated upright first.
In automations
Add the tool as a step. Its images are saved as files and the step output carries a url for each, so a later step can use {{previous_step.results.0.url}} — in an email, a webhook, or another image step's imageUrl. After a Source Files step, put {{previous_step}} in images to process every uploaded file.
Try it without code
Every field is available in the API Playground — including a file picker — and the web tool does the same thing in your browser for free, with nothing uploaded.
Choosing a format
- WebP — the safe default: much smaller than JPEG/PNG, supports transparency, works in every current browser.
- AVIF — usually the smallest at the same visual quality; slower to encode (it counts 3× toward billing).
- JPEG — maximum compatibility for photos; no transparency.
- PNG — lossless; best for screenshots, logos and anything you will edit again.