API Documentation
Integrate ImageHost into your applications with our REST API. All image data is end-to-end encrypted.
Quick Links
Base URL
All API endpoints are prefixed with /api/v1
Encryption Options
ImageHost uses AES-256-GCM encryption. How and where encryption happens depends on which upload method you use.
🚀 Which Method Should I Use?
Web Dashboard
Just use the Upload page. Encryption happens in your browser - the server never sees your images.
✓ True end-to-end encryption (client-side)
Screenshot Tools (ShareX, Flameshot)
Use /api/v1/tools/upload - send your image and the server encrypts it before storage.
curl -X POST -H "X-API-Key: YOUR_KEY" -F "file=@image.png" http://img.aither.cc/api/v1/tools/upload
⚠️ Server-side encryption: Your image travels to the server unencrypted (over HTTPS), then the server encrypts it. This is convenient but the server briefly sees your plaintext image. For maximum privacy, use the web dashboard or implement client-side encryption.
Custom Client Development
Building your own app? Implement client-side encryption using the technical details below.
✓ True end-to-end encryption (client-side)
💡 Summary: For true zero-knowledge privacy, use the web dashboard or build a custom client with client-side encryption. The tools endpoint is convenient but involves server-side encryption.
Show Technical Encryption Details (for developers)
Key Hierarchy
Password
│
└─▶ Argon2id ─▶ Master Key (256-bit)
│
└─▶ Encrypts per-image keys
│
└─▶ Image Key (256-bit, random)
│
└─▶ Encrypts image data
- Master Key: Derived from your password using Argon2id. Stored encrypted on server.
- Image Key: Random 256-bit key generated for each image. Provides key isolation.
- Share URLs: Use URL fragments (
#key) which are never sent to the server.
Encrypted File Format
All encrypted blobs use the following binary format:
┌──────────────────────────────────────────────────────────────┐ │ Offset │ Size │ Field │ ├────────┼──────────┼─────────────────────────────────────────┤ │ 0 │ 4 bytes │ Magic: "IHE1" (0x49 0x48 0x45 0x31) │ │ 4 │ 1 byte │ Version: 0x01 │ │ 5 │ 12 bytes │ IV (Initialization Vector) │ │ 17 │ N bytes │ Ciphertext (encrypted data) │ │ 17+N │ 16 bytes │ GCM Authentication Tag │ └────────┴──────────┴─────────────────────────────────────────┘
Note: The GCM auth tag is automatically appended by the encryption. Total overhead is 33 bytes (5 header + 12 IV + 16 tag).
JavaScript Example (Web Crypto API)
// Constants const MAGIC = new Uint8Array([0x49, 0x48, 0x45, 0x31]); // "IHE1" const VERSION = 0x01; const IV_LENGTH = 12; // Generate a random 256-bit key async function generateKey() { return await crypto.subtle.generateKey( { name: 'AES-GCM', length: 256 }, true, // extractable ['encrypt', 'decrypt'] ); } // Encrypt image data async function encryptImage(imageData, key) { // Generate random IV const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH)); // Encrypt with AES-256-GCM const ciphertext = await crypto.subtle.encrypt( { name: 'AES-GCM', iv: iv, tagLength: 128 }, key, imageData ); // Build output: magic + version + iv + ciphertext const header = new Uint8Array([...MAGIC, VERSION]); const result = new Uint8Array( header.length + iv.length + ciphertext.byteLength ); result.set(header, 0); result.set(iv, header.length); result.set(new Uint8Array(ciphertext), header.length + iv.length); return result; } // Export key as URL-safe base64 (for sharing) async function exportKeyForSharing(key) { const raw = await crypto.subtle.exportKey('raw', key); const base64 = btoa(String.fromCharCode(...new Uint8Array(raw))); return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); } // Usage const imageFile = /* File from input */; const imageData = await imageFile.arrayBuffer(); const key = await generateKey(); const encrypted = await encryptImage(imageData, key); const keyString = await exportKeyForSharing(key); // Upload encrypted blob to API // Share URL: https://example.com/v/abc123#${keyString}
Python Example (cryptography library)
import os import base64 from cryptography.hazmat.primitives.ciphers.aead import AESGCM # Constants MAGIC = b'IHE1' VERSION = b'\x01' IV_LENGTH = 12 KEY_LENGTH = 32 # 256 bits def generate_key() -> bytes: """Generate a random 256-bit key.""" return os.urandom(KEY_LENGTH) def encrypt_image(image_data: bytes, key: bytes) -> bytes: """Encrypt image data using AES-256-GCM.""" # Generate random IV iv = os.urandom(IV_LENGTH) # Encrypt aesgcm = AESGCM(key) ciphertext = aesgcm.encrypt(iv, image_data, None) # Build output: magic + version + iv + ciphertext return MAGIC + VERSION + iv + ciphertext def key_to_url_safe(key: bytes) -> str: """Convert key to URL-safe base64 for sharing.""" return base64.urlsafe_b64encode(key).rstrip(b'=').decode() # Usage with open('image.png', 'rb') as f: image_data = f.read() key = generate_key() encrypted = encrypt_image(image_data, key) key_string = key_to_url_safe(key) # Upload 'encrypted' to the API # Share URL: https://example.com/v/abc123#{key_string}
Decryption
To decrypt, parse the binary format and use the IV with the key:
# Python decryption example def decrypt_image(encrypted_data: bytes, key: bytes) -> bytes: # Verify magic header if encrypted_data[:4] != MAGIC: raise ValueError("Invalid magic header") # Verify version if encrypted_data[4:5] != VERSION: raise ValueError("Unsupported version") # Extract IV and ciphertext iv = encrypted_data[5:5 + IV_LENGTH] ciphertext = encrypted_data[5 + IV_LENGTH:] # Decrypt aesgcm = AESGCM(key) return aesgcm.decrypt(iv, ciphertext, None)
The encrypted_image_key Field
When uploading via the API, you must provide the per-image key encrypted with your master key. This allows you to decrypt your own images later from your gallery.
# The encrypted_image_key is the per-image key encrypted with your master key # using the same AES-256-GCM format image_key = generate_key() # Random key for this image encrypted_image_key = encrypt_with_master_key(image_key, master_key) # The server stores encrypted_image_key but cannot decrypt it # Only you can recover image_key using your master_key
Note: This client-side encryption flow is used by the web dashboard and custom clients.
The /api/v1/tools/upload endpoint uses server-side encryption instead
(see Encryption Options above for details).
URL Types & Security Trade-offs
ImageHost provides multiple URL formats with different security and usability characteristics:
| URL Type | Format | Security | Use Case |
|---|---|---|---|
| Viewer | /v/{code}#key | 🔒 E2EE - Key never sent to server | Sharing links to people, maximum privacy |
| Encrypted | /e/{code} | 🔒 E2EE - Raw encrypted blob | API access, client-side decryption |
| Embed | /i/{code}?key=... | ⚠️ Server decrypts | Forum <img> tags, BBCode |
| Thumbnail | /t/{code} | 🔒 E2EE - Encrypted thumbnail | Preview grids, galleries |
Embed URLs break end-to-end encryption
When using /i/{code}?key=..., the decryption key is sent to the server
as a query parameter. The server decrypts the image and serves it as a standard image file.
This is necessary for <img src="..."> tags to work in forums and HTML pages,
but means the server temporarily sees your image data.
How URL Fragments Preserve Privacy
The # fragment in viewer URLs is special: per HTTP specification,
browsers never send fragments to servers. The decryption key stays entirely in the browser.
https://http://img.aither.cc/v/abc123#Wj3kL9mN...
│ └─ URL-safe base64 key (never sent to server)
└─ Short code (server uses this)
Authentication
ImageHost uses JWT tokens for authentication. You can authenticate using either:
- Cookie-based: Tokens are automatically stored in HTTP-only cookies after login
- API Key: Generate an API key in Settings for programmatic access
/api/v1/auth/login
Authenticate with email and password.
Request Body (form-urlencoded)
username=user@example.com password=yourpassword
Response
{
"id": "uuid",
"username": "user",
"email": "user@example.com",
"access_token": "jwt...",
"token_type": "bearer"
}
/api/v1/auth/refresh
Get a new access token using your refresh token (stored in cookies).
/api/v1/auth/logout
Logout and invalidate your refresh token.
/api/v1/auth/me
Get the current authenticated user's information.
Response
{
"id": "uuid",
"username": "user",
"email": "user@example.com",
"storage_used_mb": 150,
"storage_quota_mb": 5000,
"created_at": "2024-01-01T00:00:00Z"
}
Images
Upload, retrieve, and manage your encrypted images. All images are encrypted client-side before upload.
/api/v1/images/upload
Upload an encrypted image.
Request Body (multipart/form-data)
encrypted_blob: (binary) # Encrypted image data encrypted_thumbnail: (binary) # Encrypted thumbnail encrypted_filename: (string) # Encrypted original filename encrypted_metadata: (string) # Encrypted metadata JSON encrypted_image_key: (string) # Image key encrypted with master key mime_type: (string) # Original MIME type is_public: (boolean) # Optional, default true expires_at: (datetime) # Optional expiration
Response
{
"id": "uuid",
"short_code": "abc123",
"view_url": "/v/abc123",
"encrypted_url": "/e/abc123",
"embed_url": "/i/abc123?key=...",
"created_at": "2024-01-01T00:00:00Z"
}
/api/v1/images
List your images with pagination.
Query Parameters
page: (int) # Page number, default 1 per_page: (int) # Items per page, default 20, max 100 sort_by: (str) # created_at, file_size, view_count order: (str) # asc or desc
/api/v1/images/{short_code}
Get image metadata by short code.
/api/v1/images/{image_id}
Update image settings.
{
"is_public": true,
"expires_at": "2024-12-31T23:59:59Z"
}
/api/v1/images/{image_id}
Delete an image (soft delete, can be restored within 30 days).
Albums
Organize your images into encrypted albums.
/api/v1/albums
Create a new album.
{
"encrypted_name": "base64...",
"encrypted_description": "base64...",
"is_public": false
}
/api/v1/albums
List your albums.
/api/v1/albums/{album_id}
Get album details and images.
/api/v1/albums/{album_id}/images
Add images to an album.
{
"image_ids": ["uuid1", "uuid2"]
}
/api/v1/albums/{album_id}
Delete an album (images are not deleted).
API Keys
Generate API keys for programmatic access. Include the key in the X-API-Key header.
curl -H "X-API-Key: your-api-key" \
http://img.aither.cc/api/v1/images
/api/v1/keys
Create a new API key.
{
"name": "My App",
"permissions": ["read", "write"],
"expires_in_days": 365
}
/api/v1/keys
List your API keys (key values are not shown).
/api/v1/keys/{key_id}
Revoke an API key.
Tools Integration
Integrate with screenshot tools like ShareX, Flameshot, and more.
/api/v1/tools/sharex-config
Download ShareX configuration file.
Requires API key authentication.
/api/v1/tools/flameshot-script
Download Flameshot upload script for Linux.
/api/v1/tools/upload
The easiest way to upload! Just send your image - we handle encryption automatically.
Request (multipart/form-data)
file: (binary) # Your image file (JPEG, PNG, GIF, WebP) is_public: (boolean) # Optional, default true link_type: (string) # Optional: "viewer" (default), "embed", or "direct"
Link Type Options
viewer
Returns /v/code#key - Private URL, key never sent to server (default)
embed
Returns /i/code?key=xxx - For <img> tags in forums/Discord
direct
Returns /e/code - Raw encrypted blob (decrypt yourself)
Examples
# Default (viewer URL - most private) curl -X POST -H "X-API-Key: your-key" -F "file=@screenshot.png" \ http://img.aither.cc/api/v1/tools/upload # Get embed URL (for forum posts, Discord) curl -X POST -H "X-API-Key: your-key" -F "file=@screenshot.png" -F "link_type=embed" \ http://img.aither.cc/api/v1/tools/upload
Response
{
"success": true,
"id": "uuid",
"short_code": "abc123",
"url": "http://img.aither.cc/v/abc123#key", // Primary URL (based on link_type)
"share_url": "http://img.aither.cc/v/abc123#key", // Always viewer URL
"embed_url": "http://img.aither.cc/i/abc123?key=xxx", // Always embed URL
"encrypted_url": "http://img.aither.cc/e/abc123", // Raw encrypted blob
"thumbnail_url": "http://img.aither.cc/t/abc123#key",
"delete_url": "http://img.aither.cc/api/v1/images/uuid",
"decryption_key": "URL-safe-base64-key",
"link_type": "viewer", // Confirms which type was used
"note": "Viewer URL returned. The decryption key..."
}
✅ Images are encrypted at rest using AES-256-GCM.
The url field contains your primary link based on link_type.
⚠️ Security note: The server briefly sees your image during encryption. For maximum security (true E2EE), use client-side encryption.
User Profile
/api/v1/users/me
Get your profile information.
/api/v1/users/me
Update your profile.
{
"username": "newusername"
}
/api/v1/users/me/password
Change your password.
{
"current_password": "...",
"new_password": "...",
"new_encrypted_master_key": "...",
"new_master_key_salt": "..."
}
Password changes require re-encrypting your master key with the new password.
/api/v1/users/me/storage
Get your storage usage statistics.
/api/v1/users/me/stats
Get detailed statistics including image count, album count, and storage breakdown.
/api/v1/users/me/sessions
List all active login sessions.
Response
{
"sessions": [
{
"session_id": "abc123...",
"ip_address": "192.168.1.1",
"device_info": "Chrome on Windows",
"created_at": "2024-01-01T00:00:00Z",
"expires_at": "2024-01-08T00:00:00Z",
"is_current": true
}
],
"total": 1
}
/api/v1/users/me/sessions/{session_id}
Terminate a specific session (log out that device).
/api/v1/users/me/sessions
Terminate all sessions except the current one (log out all other devices).
/api/v1/users/me/keys
Update your encrypted master key (used during password change or key rotation).
{
"encrypted_master_key": "base64...",
"master_key_salt": "base64..."
}
/api/v1/users/me
Permanently delete your account and all images.
⚠️ Warning: This action is irreversible. All your images will be destroyed.
Public URLs & Embedding
ImageHost provides different URL formats for different use cases. Understanding when to use each is important for both privacy and functionality.
/v/{short_code}#key
Viewer Page - Interactive viewer with download option.
The key is in the URL fragment (#) which is never sent to the server. Best for sharing via messaging apps where you control access.
/i/{short_code}?key=xxx
Embed URL - Direct image for <img> tags.
The key is in the query string (?key=) which is sent to the server.
Use for forums, Discord, or anywhere you need <img src="..."> to work.
<img src="http://img.aither.cc/i/abc123?key=YOUR_KEY" alt="Image">
/e/{short_code}
Encrypted Blob - Raw encrypted data for custom clients.
Returns the encrypted image data. You must decrypt it yourself using the decryption key. For advanced integrations building their own decryption.
/t/{short_code}
Encrypted Thumbnail - Smaller preview image (encrypted).
Returns the encrypted thumbnail. Use the same key as the full image to decrypt.
Privacy Comparison
Error Responses
All errors follow a consistent format:
{
"detail": "Error message here"
}
Common Status Codes
Rate Limits
API requests are rate limited to protect the service. Current limits:
Rate limit headers are included in responses: X-RateLimit-Limit,
X-RateLimit-Remaining, X-RateLimit-Reset