Upload Flow
Edge Proxy Pattern via Cloudflare Workers — your Secret Key never leaves your server.
AuraImage uses the Edge Proxy Pattern via Cloudflare Workers. Your Secret Key never leaves your server; all upload requests are validated at the edge before a single byte reaches storage.
Architecture Overview
[Your Backend] → Generate HMAC Signature
↓
[Your Frontend] → POST file + signature → [Cloudflare Worker]
↓
Pre-flight validation
(magic bytes, size, HMAC)
↓
[Cloudflare R2] ← stored
↓
Returns final URL → Your appStep 1 — Generate a Signature (Server-Side)
Your backend calls signUpload() using your Secret Key. This produces a short-lived HMAC token.
import { AuraImage } from '@auraimage/sdk';
const aura = new AuraImage({ secretKey: process.env.AURA_SECRET_KEY });
// POST /api/get-upload-signature
export async function POST() {
const signature = aura.signUpload({
slug: process.env.AURA_SLUG,
maxSize: '5mb', // enforced at the edge
allowedTypes: ['image/*'], // magic-byte validated
expires: Date.now() + 3_600_000, // 1 hour
});
return Response.json({ signature });
}The signature encodes constraints. An attacker who intercepts it cannot upload a larger file or a non-image format — the Worker enforces these server-side.
Step 2 — Upload from the Client
// 1. Fetch a signature from your own backend
const { signature } = await fetch('/api/get-upload-signature').then(r => r.json());
// 2. Build the form data
const formData = new FormData();
formData.append('file', file); // the image File object
formData.append('filename', 'hero-photo.jpg'); // optional; improves SEO
// 3. Upload to the AuraImage edge
const response = await fetch('https://api.auraimage.ai/v1/upload', {
method: 'POST',
headers: { 'X-Aura-Signature': signature },
body: formData,
});
const { url, blurhash, width, height } = await response.json();Upload Response
{
"url": "https://auraimage.ai/narek/hero-photo.jpg",
"blurhash": "LHF$Vb00~q~q9aM{RjxuIURjWBof",
"width": 2400,
"height": 1600,
"format": "jxl",
"size": 184320
}Edge Validation
The Cloudflare Worker runs these checks before the upload hits R2:
| Check | Description |
|---|---|
| HMAC signature | Verifies the request was authorized by your Secret Key |
| Expiry | Rejects signatures older than expires timestamp |
| Content-Length | Enforces the maxSize constraint from the signature |
| Magic Bytes | Reads the first 12 bytes of the file to confirm it is actually an image |
| Domain Whitelist | Blocks upload requests from origins not in your dashboard's allowed list |
| Rate Limiting | Per-account request throttling to prevent "wallet-drain" attacks |
HEIC / RAW Input Handling
AuraImage accepts all common input formats, including:
- HEIC (iPhone default) — converted to JXL/AVIF for web delivery; original kept in R2
- PNG, BMP, TIFF — converted on ingest; originals retained
- RAW (CR3, ARW, NEF) — supported on Pro tier and above
The stored master is a lossless JXL recompression of the original, saving ~20% space with no quality loss.
BlurHash Generation
AuraImage automatically generates a BlurHash string for every uploaded image. This is returned in the upload response and is used by the <AuraImage /> component for instant placeholder display before the real image loads.
You can also request it separately:
GET https://api.auraimage.ai/v1/blurhash/narek/hero-photo.jpgProgrammatic Upload (CLI / MCP)
The aura CLI and MCP server can upload entire directories:
# Upload all images in /public/assets and print the new URLs
aura upload ./public/assets --slug narekThe MCP migrate_assets tool does this automatically and rewrites your JSX code in the same step. See AI Integration.