Skip to content

Upload Flow

Edge Proxy Pattern — your Secret Key never leaves your server.

AuraImage uses the Edge Proxy Pattern. 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 (signUpload)

[Your Frontend]  →  POST file + signature  →  [AuraImage Edge]

                                             Pre-flight validation
                                             (HMAC, expiry, magic bytes,
                                              size, origin, rate limit, quota)

                                             [Object Storage]  ←  stored

                                             Returns final URL  →  Your app

Step 1 — Generate a signature (server-side)

Your backend calls signUpload() using your Secret Key. This produces a short-lived HMAC token.

app/api/aura/sign/route.ts
import { AuraImage } from '@auraimage/sdk';

const aura = new AuraImage({
  secretKey: process.env.AURA_SECRET_KEY!,
  projectName: process.env.NEXT_PUBLIC_AURA_PROJECT_NAME!
});

export async function POST() {
  const signature = await aura.signUpload({
    maxSize: '30mb',                // enforced at the edge
    allowedTypes: ['image/*'],      // magic-byte validated
    expiresIn: 3600                 // seconds — token lifetime
  });

  return Response.json({ signature });
}
import { Hono } from 'hono';
import { AuraImage } from '@auraimage/sdk';

const aura = new AuraImage({
  secretKey: process.env.AURA_SECRET_KEY!,
  projectName: process.env.NEXT_PUBLIC_AURA_PROJECT_NAME!
});

const app = new Hono();

app.post('/api/aura/sign', async (c) => {
  const signature = await aura.signUpload({
    maxSize: '30mb',
    allowedTypes: ['image/*'],
    expiresIn: 3600
  });
  return c.json({ signature });
});

The signature encodes your constraints. An attacker who intercepts it cannot upload a larger file or a non-image format — the edge enforces these server-side.

For non-JS backends (Python, Ruby, Go, PHP, …), implement the HMAC scheme directly. See Signature Spec.

signUpload options

OptionTypeDefaultDescription
maxSizestring | number"30mb"Max file size. Bytes (number) or human form ("500kb", "30mb", "2gb").
allowedTypesstring[]["image/*"]MIME patterns enforced via magic-byte detection.
expiresInnumber3600Token lifetime in seconds.
projectNamestringconstructor valueOverride the project for this token.
visibility"public" | "private""public"Initial visibility — see Private Images.

signUpload returns Promise<string>. Always await the call.

Step 2 — Upload from the client

// 1. Fetch a signature from your own backend
const { signature } = await fetch('/api/aura/sign', { method: 'POST' }).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://cdn.auraimage.ai/v1/upload', {
  method: 'POST',
  headers: { 'X-Aura-Signature': signature },
  body: formData
});

const { url, key, blurhash, width, height } = await response.json();

Upload response

{
  "url": "https://cdn.auraimage.ai/my-app/abc123xyz0-hero-photo.jpg",
  "key": "abc123xyz0-hero-photo.jpg",
  "blurhash": "LHF$Vb00~q~q9aM{RjxuIURjWBof",
  "width": 2400,
  "height": 1600,
  "format": "jpeg",
  "masterFormat": "jpeg",
  "size": 184320,
  "visibility": "public"
}
FieldDescription
urlPublic CDN URL for the uploaded image.
keyStored filename — pass this to getSignedUrl() / setVisibility() (see Private Images).
blurhash~30-char BlurHash string. Persist alongside the URL for instant placeholder rendering.
width / heightPixel dimensions of the original image.
formatThe format the user uploaded ("jpeg", "png", "heic", …).
masterFormatThe format actually stored on the CDN. Same as format for browser-supported types; transcoded for HEIC / TIFF / BMP.
sizeOriginal byte size.
visibility"public" or "private" based on the signature's visibility option.

url is final — store it. The randomized key prefix (10 chars) ensures same-name uploads don't collide.

Edge validation

The AuraImage edge runs these checks before the upload reaches storage:

CheckBehavior on failure
HMAC signature401 — Verifies the request was authorized by your Secret Key.
Token expiry401 — Rejects tokens past their expiresIn window.
Reserved project names400api, admin, cdn, health, registry, static, test, v1 are reserved.
Allowed origins403 — Blocks uploads from origins not in your dashboard's allow-list (when configured).
Rate limiting429 — Per-project request throttling.
Quota402 — Plan storage / wallet-guard exceeded.
Content-Type400 — Must be multipart/form-data.
maxSize413 — Enforces the size constraint from the signature.
Magic bytes415 — Reads the first 12 bytes to confirm it's actually an image.
allowedTypes415 — Detected format must match the signature's allow-list.
Tier-gated formats403 — HEIC / HEIF require Pro; TIFF / BMP require Startup.

Input format handling

AuraImage accepts all common input formats:

  • JPEG, PNG, WebP, AVIF, GIF — stored as-is.
  • HEIC / HEIF (iPhone default) — Pro tier and above. Transcoded to a CDN-compatible master on ingest.
  • TIFF / BMP — Startup tier. Transcoded to PNG or JPEG on ingest.
  • JXL — not supported; returns 415.

The format field in the upload response reflects what you uploaded; masterFormat reflects what's stored. On delivery, the master is transcoded to AVIF, WebP, or JPEG via Accept-header negotiation. See URL API for content negotiation details.

BlurHash generation

AuraImage automatically computes a BlurHash for every uploaded image and returns it in the upload response. The <AuraImage /> component uses it for instant placeholder display before the full image arrives.

You can also fetch it later:

GET https://cdn.auraimage.ai/v1/blurhash/<projectName>/<filename>
→ { "blurhash": "LHF$Vb00~q~q9aM{RjxuIURjWBof" }

Programmatic upload (CLI / MCP)

The aura CLI uploads entire directories:

# Upload all images in /public/assets and print the new URLs
aura upload ./public/assets --project-name my-app

# Emit newline-delimited JSON (filename, url, key, blurhash, dimensions, …) for scripting
aura upload ./public/assets --project-name my-app --json | jq '.url'

The MCP migrate_assets tool does this automatically and rewrites your JSX in the same step. See AI Integration.