Error Handling
This document specifies error responses and the client handling requirements for each.
HTTP Status Code Reference
Section titled “HTTP Status Code Reference”| Status | Meaning | Client Requirement |
|---|---|---|
| 200 | Success | Continue processing |
| 201 | Created | Resource created successfully |
| 204 | No Content | Deletion successful |
| 400 | Bad Request | Fix the request format before retrying |
| 401 | Unauthorized | Obtain a new token before retrying |
| 403 | Forbidden | Complete verification before retrying |
| 404 | Not Found | Verify the resource exists |
| 409 | Conflict | Resolve the conflict (resource exists) |
| 429 | Rate Limited | Wait and retry with backoff |
| 500 | Server Error | Retry with exponential backoff |
| 502 | Bad Gateway | Retry; the leader may have changed |
| 503 | Service Unavailable | Wait and retry |
| 504 | Gateway Timeout | Retry; view is catching up |
Authentication Errors
Section titled “Authentication Errors”401 Unauthorized
Section titled “401 Unauthorized”The service returns 401 when the token is missing, expired, invalid, or revoked.
Client Requirements:
- Obtain a new token via
/auth/tokenor/auth/login - Do not retry with the same token
- Implement automatic token refresh
try { await api.listLogs();} catch (error) { if (error.status === 401) { // Refresh token before retry const newToken = await api.exchangeToken(apiKey); // Retry with new token }}403 Forbidden
Section titled “403 Forbidden”The service returns 403 when authentication succeeds but authorization fails.
Common Causes:
- Login with unverified email
- Accessing resources outside tenant scope
Client Requirements:
- For unverified email: Verify email before retrying
- For scope issues: Do not retry
Rate Limiting
Section titled “Rate Limiting”429 Too Many Requests
Section titled “429 Too Many Requests”The service returns 429 when the tenant exceeds the rate limit.
Response Headers:
Retry-After: 1Client Requirements:
- Respect the
Retry-Afterheader value - Implement exponential backoff with jitter
- Consider implementing client-side rate limiting to avoid hitting limits
async function withRateLimitHandling<T>( fn: () => Promise<T>, maxRetries: number = 5): Promise<T> { for (let attempt = 0; attempt < maxRetries; attempt++) { try { return await fn(); } catch (error) { if (error.status === 429) { const retryAfter = parseInt(error.headers.get('Retry-After') || '1'); const backoff = retryAfter * 1000 * Math.pow(2, attempt); const jitter = Math.random() * 1000; await sleep(backoff + jitter); continue; } throw error; } } throw new Error('Max retries exceeded');}View Errors
Section titled “View Errors”503 Service Unavailable (No Leader)
Section titled “503 Service Unavailable (No Leader)”The service returns 503 when no leader exists for the view.
Cause: Leadership election is in progress or the view has no healthy nodes.
Client Requirements:
- Retry with exponential backoff
- Check view stats to monitor leader status
- If error persists, the view may require redeployment
502 Bad Gateway (Leader Unreachable)
Section titled “502 Bad Gateway (Leader Unreachable)”The service returns 502 when the request cannot be proxied to the leader.
Cause: The leader changed during request routing or is temporarily unreachable.
Client Requirements:
- Retry immediately (leader may have changed)
- Implement backoff if retries fail
- Optionally check view stats for current leader
504 Sequence Wait Timeout
Section titled “504 Sequence Wait Timeout”The service returns 504 when the view cannot reach the requested sequence within the timeout (4 seconds).
Response:
{ "error": "Timeout waiting for view {view_key} to catch up (target: {sequence}, current: {current})"}Cause: The view has not processed up to the requested sequence number within the server-enforced 4-second timeout.
Client Requirements:
- Implement retry with exponential backoff
- Check the
currentvalue in the error to assess progress - Optionally fall back to stale reads if consistency is not critical
async function queryWithRetry( logName: string, viewName: string, input: Uint8Array, after: number): Promise<Uint8Array> { for (let i = 0; i < 5; i++) { try { return await api.queryView(logName, viewName, input, after); } catch (error) { // Retry on timeout or leader change const isRetryable = error.status === 504 || error.status === 502; if (isRetryable) { await sleep(100 * Math.pow(2, i)); continue; } throw error; } } // Optional: Fall back to stale read return await api.queryView(logName, viewName, input);}Log Errors
Section titled “Log Errors”409 Conflict (Log Already Exists)
Section titled “409 Conflict (Log Already Exists)”The service returns 409 when attempting to create a log that already exists.
Client Requirements:
- Do not retry with the same log name
- Use the existing log or choose a different name
WASM View Errors
Section titled “WASM View Errors”View Creation Failed (400)
Section titled “View Creation Failed (400)”The service returns 400 when the WASM binary is invalid.
Client Requirements:
- Verify the WASM implements
primatomic:machineinterface - Binary size must be under 100 MB
- Binary must be built for
wasm32-unknown-unknowntarget (no WASI imports)
View Panic/Trap (500)
Section titled “View Panic/Trap (500)”The service may return 500 when the WASM component crashes during execution.
Client Requirements:
- Add error handling in WASM code
- Handle edge cases gracefully in
snapshotandrestoreimplementations - View may need redeployment with fixed component
Diagnostic Endpoints
Section titled “Diagnostic Endpoints”Use these endpoints for debugging:
View Status
Section titled “View Status”curl .../logs/my-log/views/my-view/stats \ -H "Authorization: Bearer $TOKEN"Returns leader status, processed sequence, and error information.
Log Statistics
Section titled “Log Statistics”curl .../logs/my-log/stats \ -H "Authorization: Bearer $TOKEN"Returns event count and storage metrics.
Usage Monitoring
Section titled “Usage Monitoring”curl .../billing/usage \ -H "Authorization: Bearer $TOKEN"Returns current usage metrics for rate limit monitoring.
Storage is reported as GB-seconds. Divide by 3,600 to convert to GB-hours.