Skip to content

Error Handling

This document specifies error responses and the client handling requirements for each.

StatusMeaningClient Requirement
200SuccessContinue processing
201CreatedResource created successfully
204No ContentDeletion successful
400Bad RequestFix the request format before retrying
401UnauthorizedObtain a new token before retrying
403ForbiddenComplete verification before retrying
404Not FoundVerify the resource exists
409ConflictResolve the conflict (resource exists)
429Rate LimitedWait and retry with backoff
500Server ErrorRetry with exponential backoff
502Bad GatewayRetry; the leader may have changed
503Service UnavailableWait and retry
504Gateway TimeoutRetry; view is catching up

The service returns 401 when the token is missing, expired, invalid, or revoked.

Client Requirements:

  • Obtain a new token via /auth/token or /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
}
}

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

The service returns 429 when the tenant exceeds the rate limit.

Response Headers:

Retry-After: 1

Client Requirements:

  • Respect the Retry-After header 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');
}

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

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

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 current value 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);
}

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

The service returns 400 when the WASM binary is invalid.

Client Requirements:

  • Verify the WASM implements primatomic:machine interface
  • Binary size must be under 100 MB
  • Binary must be built for wasm32-unknown-unknown target (no WASI imports)

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 snapshot and restore implementations
  • View may need redeployment with fixed component

Use these endpoints for debugging:

Terminal window
curl .../logs/my-log/views/my-view/stats \
-H "Authorization: Bearer $TOKEN"

Returns leader status, processed sequence, and error information.

Terminal window
curl .../logs/my-log/stats \
-H "Authorization: Bearer $TOKEN"

Returns event count and storage metrics.

Terminal window
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.