Errors
Three classes cover every failure path in sonuslab-storage.
Error classes
| Parameter | Type | Description |
|---|---|---|
StorageApiError | server SDK | Non-2xx response from storage-api. Exposes .status (number) and .body (parsed JSON or string). |
UploadError | browser | Network or PUT failure in the browser helper. Exposes .cause with the underlying error. |
WebhookVerificationError | webhook | Thrown by verifyWebhook on bad signature, expired timestamp, or malformed payload. |
StorageApiError
Every server SDK call rejects with this class on a non-2xx response. Switch on .status to map to your own HTTP responses.
import { StorageApiError } from 'sonuslab-storage/server'
try {
await storage.upload({ name, contentType, data })
} catch (err) {
if (err instanceof StorageApiError) {
if (err.status === 401) // invalid or revoked API key
if (err.status === 413) // file exceeds maxFileSize
if (err.status === 404) // file id not found
if (err.status === 429) // rate limited (see below)
console.error(err.status, err.body)
}
throw err
}Common statuses
| Parameter | Type | Description |
|---|---|---|
400 | Bad Request | Request body failed validation. .body has the field-level error. |
401 | Unauthorized | API key missing, invalid, or revoked. |
404 | Not Found | File or upload id does not exist (or belongs to a different app). |
413 | Payload Too Large | size exceeds the app maxFileSize. Use multipart instead. |
429 | Too Many Requests | Rate limit hit. Honour the Retry-After header. |
5xx | Server Error | Transient storage-api error. Retry with exponential backoff. |
WebhookVerificationError
Thrown by verifyWebhook when:
- The signature header is missing or malformed.
- The HMAC doesn't match (wrong secret, modified body).
- The timestamp is older than the tolerance window (default 300s).
Always respond with 401 — do not leak which check failed.
import { WebhookVerificationError } from 'sonuslab-storage/webhook'
try {
verifyWebhook({ body, signature, secret })
} catch (err) {
if (err instanceof WebhookVerificationError) {
throw createError({ statusCode: 401, statusMessage: 'Invalid signature' })
}
throw err
}UploadError
Wraps any failure during the browser upload flow — presign request, the direct PUT, abort, or finalize. The original error is preserved in .cause.
import { UploadError } from 'sonuslab-storage/client'
try {
await uploadFile({ file, presignEndpoint, completeEndpoint })
} catch (err) {
if (err instanceof UploadError) {
console.error('upload failed:', err.message, err.cause)
toast.error('Could not upload — please try again')
return
}
throw err
}Patterns
Map server errors to HTTP responses
export default defineEventHandler(async (event) => {
try {
return await storage.presign(await readBody(event))
} catch (err) {
if (err instanceof StorageApiError) {
throw createError({ statusCode: err.status, statusMessage: err.body?.message ?? 'Upload error' })
}
throw err
}
})Retry on 5xx / 429
async function withRetry<T>(fn: () => Promise<T>, attempts = 3): Promise<T> {
for (let i = 0; i < attempts; i++) {
try {
return await fn()
} catch (err) {
const retryable = err instanceof StorageApiError && (err.status === 429 || err.status >= 500)
if (!retryable || i === attempts - 1) throw err
await new Promise((r) => setTimeout(r, 250 * 2 ** i))
}
}
throw new Error('unreachable')
}
const file = await withRetry(() => storage.upload({ name, contentType, data }))