Conversion API
Send an image, get it back converted, resized, watermarked or stripped of its metadata. Nothing is stored, the answer is the file itself.
Quick start
Grab your key
The same one the upload API uses, in the dashboard.
POST the image
Multipart form data to /api/v1/convert.
Write the bytes
The body is the image. No JSON to unwrap.
Endpoint
https://imageserver.pw/api/v1/convert
multipart/form-data with the image in a part called
file. Authenticate with the
X-IMAGESERVER-AUTH-KEY header, or a
userKey body field for clients that cannot set headers.
https://imageserver.pw/api/v1/convert
Answers with the accepted values and their bounds as JSON, so a client can check them without reading this page. No key needed.
Request fields
| Field | Required | Description |
|---|---|---|
file |
yes | The image. JPEG, PNG, WebP, GIF, TIFF or AVIF, and it has to be one your plan allows. |
userKey |
no | Your user key as a body field. The header wins if both are present. |
format |
no |
keep (default), webp,
jpeg or png.
|
quality |
no |
40 to 100, default
82. For JPEG and WebP; PNG is lossless and ignores it.
|
maxEdge |
no |
64 to 10000.
Caps the longest edge and keeps the aspect ratio. Never enlarges a smaller picture.
|
strip |
no |
true by default. Removes camera and location data.
The EXIF rotation is applied first, so a phone photo does not come back on its side.
|
watermark |
no | Text, up to 60 characters. Sending nothing means no watermark. |
watermarkPosition |
no |
bottom-right (default), bottom-left,
top-right, top-left or
centre.
|
watermarkOpacity |
no | 5 to 100, default 65. |
watermarkSize |
no |
1 to 20, default
4. Its height as a percentage of the short edge.
|
A value outside its range is pulled to the nearest end rather than rejected, so a slightly wrong request still gives you a usable image.
Examples
curl -X POST https://imageserver.pw/api/v1/convert \
-H "X-IMAGESERVER-AUTH-KEY: your-user-key" \
-F "file=@/path/to/photo.png" \
-F "format=webp" \
-F "quality=82" \
-F "maxEdge=1920" \
-o photo.webp
$headers = @{ "X-IMAGESERVER-AUTH-KEY" = "your-user-key" }
$form = @{
file = Get-Item -Path "C:\path\to\photo.png"
format = "webp"
quality = "82"
maxEdge = "1920"
}
Invoke-WebRequest -Uri "https://imageserver.pw/api/v1/convert" `
-Method Post -Headers $headers -Form $form `
-OutFile "photo.webp"
import { readFile, writeFile } from 'node:fs/promises';
const form = new FormData();
form.append('file', new Blob([await readFile('photo.png')]), 'photo.png');
form.append('format', 'webp');
form.append('maxEdge', '1920');
const res = await fetch('https://imageserver.pw/api/v1/convert', {
method: 'POST',
headers: { 'X-IMAGESERVER-AUTH-KEY': 'your-user-key' },
body: form,
});
if (!res.ok) throw new Error((await res.json()).error);
console.log('applied:', res.headers.get('X-Image-Applied'));
console.log('left today:', res.headers.get('RateLimit-Remaining'));
await writeFile('photo.webp', Buffer.from(await res.arrayBuffer()));
import requests
with open("photo.png", "rb") as handle:
response = requests.post(
"https://imageserver.pw/api/v1/convert",
headers={"X-IMAGESERVER-AUTH-KEY": "your-user-key"},
files={"file": ("photo.png", handle)},
data={"format": "webp", "quality": "82", "maxEdge": "1920"},
)
response.raise_for_status()
print("applied:", response.headers["X-Image-Applied"])
with open("photo.webp", "wb") as out:
out.write(response.content)
Response
On success the body is the image, with the content type of whatever came out. Nothing to unwrap, and nothing kept on our side. Every answer, including the refusals, carries these:
| Header | Meaning |
|---|---|
X-Image-Applied |
What actually happened, for example resized to 1920px, metadata removed, converted to WebP.
Reads none when the request asked for nothing.
|
RateLimit-Limit |
Conversions your plan allows per day. |
RateLimit-Remaining |
How many are left. Read this rather than counting yourself. |
RateLimit-Reset |
Seconds until the allowance resets, which is midnight UTC. |
Errors
| Status | Message | What to do |
|---|---|---|
400 |
No file was sent. | The part has to be called file. |
400 |
File content does not match its extension. | The bytes are checked as well as the name. Send the file under its real extension. |
401 |
Missing or invalid user key. | Copy the key again from the dashboard, it may have been regenerated. |
402 |
Not included in your plan. | The body carries an upgradeUrl. See plans. |
403 |
Your account is suspended. | Contact us on Discord. |
413 |
File exceeds your plan limit of X. | The message names your actual limit. Shrink the file or upgrade. |
422 |
Nothing was done, with the reason. | Usually animated. See the note below. |
429 |
Too fast, or out of allowance for the day. | Read RateLimit-Reset and wait that many seconds. |
A request refused before any work happens, for a missing file, the wrong type or a size over your limit, does not cost you part of the day's allowance.
An animation is refused, not flattened
Converting an animated GIF or WebP the obvious way returns its first frame and nothing
else. Rather than hand back a picture where a clip was expected, the endpoint answers
422 with "reason": "animated"
and leaves your file alone.
Just the one image?
The browser tools do the same conversions without a key, without a quota and without the file ever leaving your machine.