Upload API
Send a file over plain HTTP and get back an encrypted share link and a deletion link. No SDK, no session, one header.
Quick start
Grab your key
It is the AUTH-Key under Settings › Security in your dashboard.
POST the file
Multipart form data to /api/v1/upload.
Read the JSON
You get url and deletionUrl.
Endpoint
https://imageserver.pw/api/v1/upload
multipart/form-data
X-IMAGESERVER-AUTH-KEY
Request fields
| Field | Required | Description |
|---|---|---|
file |
yes | The file itself. The extension of the sent filename decides the detected MIME type, which is then checked against your plan. Text files (.txt, .log, .json, .md and common source code) become pastes on every plan, up to 1 MB of UTF-8, with an optional language field for highlighting. |
userKey |
no | Your user key as a body field, for clients that cannot set headers. The header wins if both are present. |
urlStyle |
no |
embed, raw or private.
Decides which URL you get back, see URL styles.
Leave it out to get the URL style set in your dashboard, which is
private for a new account.
|
domains |
no |
A JSON array as a string. ["own"] serves the file from one of your verified domains,
["default"] from whichever domain the server is set to, and the slug of a domain category from that group,
for example ["short"]. The config generator
lists the categories that exist. Anything unknown, including a hostname, falls back to a random public domain, and with several entries one is chosen at random.
|
title |
no | Embed title shown by Discord and friends. Defaults to imageserver.pw. |
color |
no | Embed accent color as hex, for example #ef7c3b, which is also the default. |
expires |
no |
How long the upload is kept:
never,
1h,
6h,
12h,
24h,
7d or
30d, counted from the upload.
Sending this beats the "Expire after" setting in your dashboard, including
never, so a client can decide per file.
Leave it out to use the setting. An unknown value is ignored rather than rejected,
and the setting applies.
Expired uploads stop resolving immediately and are deleted by an hourly job;
once deleted, the file is gone and cannot be restored.
|
maxDownloads |
no |
How often the upload may be fetched:
unlimited,
1,
5,
10,
25 or
100.
Works the same way as expires and alongside it:
sending it beats the "Download limit" setting in your dashboard, including
unlimited, and whichever of the two runs out
first ends the link.
Counted per visitor rather than per request, so a video player asking for byte ranges
spends one download, and a request with the wrong key spends none.
Once the last one is used the link stops working immediately and the file is queued
for deletion. See making a link stop working.
|
folder |
no |
Name of the folder the upload is filed into. The folder is
created on first use, so no setup in the dashboard is needed, and the name is
matched without regard to case:
Clips and clips are
the same folder. Leave it out and the upload stays uncategorised.
Names longer than 60 characters are cut to 60. An account holds at most 200
folders; past that, a new name is not created and the upload stays uncategorised
rather than failing.
|
tags |
no |
Comma-separated tags, at most 10 per upload and 32 characters each. They are
lowercased, spaces become hyphens and anything else is dropped, so
CS:GO, Game Clips is stored as
csgo and game-clips.
Unlike the folder, a file can carry several.
|
Account settings can override your request
If "Use these settings for ShareX too" is on in your dashboard's upload settings, the server ignores
urlStyle,
domains,
title and
color from the request and uses your saved
preferences instead. If your parameters seem to have no effect, this switch is the first thing to check.
With it off, your request decides, and anything it leaves out falls back to the same saved
preferences. So an uploader config that never sets
urlStyle
follows your dashboard rather than a fixed default, and a config that does set it is obeyed.
The switch only matters when the two disagree.
expires,
maxDownloads,
folder and
tags are the exceptions and are
never overridden. Retention and filing are per-file decisions, so a client that offers them before each
upload stays in control regardless of this switch.
URL styles
embed
https://imageserver.pw/aB3dK9.png?key=…
A preview page with Open Graph tags, so Discord, Twitter and Slack render a card.
raw
https://imageserver.pw/raw/aB3dK9.png?key=…
The decrypted bytes with the right content type and nothing around them. Use it for
<img src> and for anything that expects a real file.
private (new accounts)
https://imageserver.pw/p/aB3dK9.png#key=…
The key sits behind #, so browsers never
transmit it and the file is decrypted locally. A crawler cannot open the file, so the
link preview in Discord and elsewhere comes from a small separate image made at upload
time: blurred by default, a full thumbnail, or none, as set under "Preview Image" in
your dashboard. That image is the one part our server can read, see
what we can see.
Examples
curl -X POST https://imageserver.pw/api/v1/upload \
-H "X-IMAGESERVER-AUTH-KEY: your-user-key" \
-F "file=@/path/to/image.png" \
-F "urlStyle=embed" \
-F "color=#ef7c3b"
$headers = @{ "X-IMAGESERVER-AUTH-KEY" = "your-user-key" }
$form = @{
file = Get-Item -Path "C:\path\to\image.png"
urlStyle = "embed"
color = "#ef7c3b"
}
$result = Invoke-RestMethod -Uri "https://imageserver.pw/api/v1/upload" `
-Method Post -Headers $headers -Form $form
$result.url
const form = new FormData();
form.append('file', file); // File or Blob
form.append('urlStyle', 'embed');
form.append('color', '#ef7c3b');
const res = await fetch('https://imageserver.pw/api/v1/upload', {
method: 'POST',
headers: { 'X-IMAGESERVER-AUTH-KEY': 'your-user-key' },
body: form
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
console.log(data.url, data.deletionUrl);
import requests
with open("/path/to/image.png", "rb") as fh:
response = requests.post(
"https://imageserver.pw/api/v1/upload",
headers={"X-IMAGESERVER-AUTH-KEY": "your-user-key"},
files={"file": fh},
data={"urlStyle": "embed", "color": "#ef7c3b"},
timeout=60,
)
response.raise_for_status()
print(response.json()["url"])
Response
200 OK, from /api/v1/upload
{
"url": "https://imageserver.pw/aB3dK9.png?key=Xq2…",
"deletionUrl": "https://imageserver.pw/delete/aB3dK9?key=9f4…"
}
-
Store
deletionUrl. It is the only way to remove the file without logging in, and it is never shown again. -
The host in both URLs is whichever domain was picked, so it is not always
imageserver.pw. Never build the link yourself, use the one you get back.
Deleting an upload
/delete/:key?key=deletion-key
Open the deletionUrl
from the upload response in a browser. It shows a page that names the file and asks you to confirm; the file
is deleted, immediately and for good, only when you press the button there. No auth key is involved, the
deletion key in the query string is the credential, so keep the link to yourself.
Opening the link deletes nothing on its own. Chat apps, link scanners and browsers fetch links ahead of time, and a link that deleted on sight would let any of them remove your file. For the same reason a plain GET from a script only gets that confirmation page back, as HTML: there is no API endpoint that deletes with the auth key. Requests are limited to 10 per minute.
Signed in, you can delete the same files from the dashboard without the key.
Errors
| Status | Message | What to do |
|---|---|---|
400 |
Missing auth key or file. | Check the header name and that the file part is called file. |
400 |
Unsupported file type for your plan. | The type is taken from the filename extension. See the allowed list on plans. |
401 |
Invalid user key. | Copy the key again from the dashboard, it may have been regenerated. |
403 |
Your account is suspended. | Uploads stay disabled until the suspension is lifted. Contact us on Discord. |
400 |
File content does not match its extension. | The bytes are checked against the extension. Send the file under its real extension. |
403 |
This file cannot be uploaded. | The file matches one that was removed from this site. It cannot be uploaded again. |
413 |
File size exceeds your plan limit of X. | The message names your actual limit. Compress the file or upgrade. |
413 |
Storage quota exceeded. Remaining: X. | Delete old uploads in the dashboard or raise the quota. |
413 |
File size exceeds limit (500MB) | Plain text, not JSON: the request was cut off before the upload was read. 500 MB is the most any request can carry. |
429 |
Too many uploads. Please wait a moment before trying again. | 30 per minute, counted per user key and shared with the Paste API. Back off and retry. |
429 |
New accounts can upload 30 files a day during their first week. … | Confirming an email address lifts it at once; otherwise it ends when the account is a week old. |
429 |
Too many requests with an invalid user key. Try again in a few minutes. | Several wrong keys in a row from one client. Fix the key, then wait a few minutes. |
500 |
Server error occurred during file upload. | Plain text, not JSON. Retry once, then tell us on Discord. |
Except for the 500 above, errors are JSON in the shape { "error": "..." }.
ShareX
You do not have to build the preset by hand.
- Generate it in the Config Creator, or copy the ready-made one from the homepage
- ShareX, Destinations › Custom Uploader Settings › Import › From Clipboard
- Under Destinations, select it as image, text and file uploader
The generated preset posts to /api/v1/upload, so it always gets the JSON body and can fill in
DeletionURL.
Postman
- New request,
POST https://imageserver.pw/api/v1/upload - Headers, add
X-IMAGESERVER-AUTH-KEY - Body, choose form-data
- Key
file, type File, then pick your file - Optionally add
urlStyle,color,title,domains,folder,tags
Leave Content-Type unset, Postman writes the multipart boundary itself.
Everything you upload is encrypted
The server generates a random password per file, encrypts with AES-256, and hands the password back to you inside the URL. We keep no copy we can read, so unless a key pair of yours sealed one, a lost link is a lost file.
How the encryption worksDo not want to write code?
The desktop client does the same thing behind a hotkey: capture, upload, link in your clipboard. Windows, macOS and Linux.