Home Upload How it works Plugins API FAQ Contact

API Reference

Programmatic access to upload, list, fetch, and delete end-to-end encrypted images. All endpoints are under https://uploadimages.org.

Overview

The v1 API is a small JSON-over-HTTPS surface. You authenticate with a Bearer key, send fields as either JSON or multipart, and receive JSON in return (except GET /images/:id, which returns raw bytes). Every state-changing endpoint is rate-limited and scoped.

The server never sees image plaintext. Your client encrypts with AES-256-GCM before uploading, then puts the decryption key in the URL fragment (#…) so it never reaches the server. See how it works.

Quickstart

  1. Register an account, wait for admin activation.
  2. Sign in → Account → Keys → create a key with read,write,delete scopes.
  3. Upload your first image with the example below.
curl -X POST https://uploadimages.org/api/v1/images \
  -H "Authorization: Bearer uimg_live_..." \
  -F "[email protected]" \
  -F "visibility=public"
const form = new FormData();
form.append('file', fileInput.files[0]);
form.append('visibility', 'public');

const r = await fetch('https://uploadimages.org/api/v1/images', {
  method: 'POST',
  headers: { 'Authorization': 'Bearer uimg_live_...' },
  body: form
});
const data = await r.json();
console.log(data.share_link);
import requests

with open('cat.jpg', 'rb') as f:
    r = requests.post(
        'https://uploadimages.org/api/v1/images',
        headers={'Authorization': 'Bearer uimg_live_...'},
        files={'file': f},
        data={'visibility': 'public'},
    )
print(r.json()['share_link'])

Authentication

Most endpoints take a Bearer token in the Authorization header. Tokens look like uimg_live_32-hex and are checked in constant time.

Header
Authorization: Bearer uimg_live_9a6b0ab6b91114e495ff9be56b994e51

Scopes

Each key carries fixed scopes. Endpoint failures with code 403 tell you which scope is missing.

ScopeWhat it grants
readList your images, fetch metadata, view usage and events
writeUpload new images
deleteDelete single images or bulk-delete
adminRotate keys via the API; rarely needed (use the dashboard instead)
Isolation: every endpoint that accepts an :image_id returns 404 if the image isn't yours, not 403. The server never reveals whether someone else's image exists.

Errors

All errors are JSON: {"error": "human-readable string"}.

StatusWhen
400Request was malformed or a field failed validation.
401Missing or invalid bearer token.
403Your key, account, or quota state doesn't permit this action. The body explains which.
404The resource doesn't exist or isn't visible to you.
413File too large. The current upload limit is configured by the administrator.
429You're sending requests faster than allowed. Wait briefly and retry.
503The service is temporarily unavailable.

Images

POST /api/v1/images requires write

Upload an image. multipart/form-data. Returns the id, share link, and a control token (save it; needed to delete via the legacy non-bearer endpoint).

Body fields

FieldTypeRequiredNotes
filefileyesImage bytes (or ciphertext if e2e=true)
visibilitystringnopublic or private (default private)
ttlintegernoauto-delete after N seconds; 60 – 31,536,000
passwordstringno≤128 chars; viewer must supply X-Password
nsfwboolnotrue / 1 / on
e2eboolnoTells the server the file is pre-encrypted ciphertext, not a decodable image
original_mimestringif e2eThe plaintext MIME the client encrypted

Example

curl -X POST https://uploadimages.org/api/v1/images \
  -H "Authorization: Bearer uimg_live_..." \
  -F "[email protected]" \
  -F "visibility=public" \
  -F "ttl=86400"
const form = new FormData();
form.append('file', file);
form.append('visibility', 'public');
form.append('ttl', '86400');

const r = await fetch('/api/v1/images', {
  method: 'POST',
  headers: { 'Authorization': 'Bearer uimg_live_...' },
  body: form,
});
const { image_id, share_link, control_token } = await r.json();
r = requests.post(
    'https://uploadimages.org/api/v1/images',
    headers={'Authorization': 'Bearer uimg_live_...'},
    files={'file': open('cat.jpg', 'rb')},
    data={'visibility': 'public', 'ttl': '86400'},
)
print(r.json())

Response 200

{
  "image_id":           "a1b2c3",
  "share_link":         "https://uploadimages.org/a1b2c3#KEY...",
  "original_size":      48291,
  "mime_type":          "image/jpeg",
  "control_token":      "abcdef0123456789abcdef0123456789",
  "password_protected": false,
  "owner_id":           "0399dbb24639d8a6"
}
GET /api/v1/images requires read

List images owned by your account. Pagination via limit / offset.

Query parameters

ParamTypeDefaultNotes
limitint20max 100
offsetint0
visibilitystringanypublic / private
GET /api/v1/images/:image_id requires read

Metadata for one image. 404 if it isn't yours or doesn't exist (existence is not leaked).

DELETE /api/v1/images/:image_id requires delete

Delete one image you own. Idempotent: deleting a missing id returns 404.

POST /api/v1/images/bulk_delete requires delete

Delete up to 200 images in one call. Ids you don't own are silently surfaced as not_found.

Body

{ "image_ids": ["a1b2c3", "d4e5f6", "..."] }

Account

GET /api/v1/users/me any scope

Your profile, quotas, and current keys (previews only. Raw key bytes are never returned).

GET /api/v1/users/me/usage any scope

Lifetime & per-day counters: uploads, bytes, active images, quotas.

GET /api/v1/users/me/events any scope

Audit log: signin, key create, key revoke, password change, recent uploads. Up to 200 entries newest-first.

GET /api/v1/users/me/keys any scope

List your API keys. Each entry has a preview, scopes, label, created/last-used timestamps. The raw key secret is never returned.

POST /api/v1/users/me/keys session auth (cookie + CSRF)

Mint a new key. The response includes the raw secret once; save it. Bearer auth is not accepted here. A leaked key can't mint replacements for itself.

DELETE /api/v1/users/me/keys/:key_id session auth (cookie + CSRF)

Revoke one of your keys. Scoped: you can only revoke keys belonging to your account.


Auth

POST /api/v1/auth/login no auth

Exchange email + password for a session cookie (HttpOnly, SameSite=Lax). Returns a CSRF token for use in state-changing session calls.

POST /api/v1/auth/logout session

Revoke the current session cookie server-side and instruct the browser to drop it.

GET /api/v1/auth/me session

Return the current session's user + a fresh CSRF token for the SPA. 401 if not signed in.