Error Handling
All Aerostack SDK methods throw typed errors on failure. This guide covers the error types, how to catch and handle them, and strategies for retrying failed operations.
Beta — Error types are stable but new error codes may be added in minor versions.
Error types
The SDK throws specific error classes depending on what went wrong:
| Error Class | When it occurs | HTTP Status |
|---|---|---|
ClientError | Invalid request, missing parameters, bad input | 400 |
AuthenticationError | Invalid or expired token, unauthorized access | 401 |
ForbiddenError | Valid token but insufficient permissions | 403 |
NotFoundError | Resource does not exist | 404 |
ValidationError | Input fails server-side validation rules | 422 |
RateLimitError | Too many requests | 429 |
ServerError | Unexpected server failure | 500+ |
NetworkError | Connection failed, timeout, DNS failure | N/A |
Error properties
Every SDK error includes:
interface AerostackError {
message: string // Human-readable error description
code: string // Machine-readable error code (e.g., 'AUTH_INVALID_TOKEN')
status: number // HTTP status code
details?: any // Additional context (validation errors, rate limit info)
}Basic try/catch
import { useAuth } from '@aerostack/react'
function LoginForm() {
const { signIn, error, loading } = useAuth()
const handleSubmit = async (email: string, password: string) => {
try {
await signIn(email, password)
// Success -- user and tokens are set
} catch (err) {
// err.message is also reflected in the `error` state
if (err.code === 'AUTH_INVALID_CREDENTIALS') {
// Wrong email or password
} else if (err.code === 'AUTH_ACCOUNT_LOCKED') {
// Too many failed attempts
} else if (err.code === 'AUTH_EMAIL_NOT_VERIFIED') {
// Redirect to verification page
}
}
}
return (
<div>
{/* The error state is set automatically by useAuth */}
{error && <p style={{ color: 'red' }}>{error}</p>}
</div>
)
}Common error codes
Authentication errors
| Code | Description |
|---|---|
AUTH_INVALID_CREDENTIALS | Wrong email or password |
AUTH_INVALID_TOKEN | Access token is malformed or expired |
AUTH_ACCOUNT_LOCKED | Account locked due to too many failed attempts (60-minute lockout) |
AUTH_EMAIL_NOT_VERIFIED | Email verification required before sign-in |
AUTH_USER_NOT_FOUND | No account with this email |
AUTH_EMAIL_ALREADY_EXISTS | Email already registered |
AUTH_OTP_EXPIRED | OTP code has expired (10-minute window) |
AUTH_OTP_INVALID | Wrong OTP code |
AUTH_REFRESH_TOKEN_INVALID | Refresh token expired or revoked |
Database errors
| Code | Description |
|---|---|
DB_QUERY_ERROR | SQL syntax error or constraint violation |
DB_CONSTRAINT_VIOLATION | Unique constraint, foreign key, or NOT NULL violation |
DB_TABLE_NOT_FOUND | Referenced table does not exist |
Storage errors
| Code | Description |
|---|---|
STORAGE_FILE_TOO_LARGE | Upload exceeds size limit |
STORAGE_FILE_NOT_FOUND | Requested file does not exist |
STORAGE_QUOTA_EXCEEDED | Storage quota for the project is full |
Rate limit errors
| Code | Description |
|---|---|
RATE_LIMIT_EXCEEDED | Too many requests in the time window |
AUTH_OTP_VERIFY_LIMIT | Too many OTP verification attempts (max 3) |
Retry strategies
Simple retry with backoff
async function withRetry<T>(
fn: () => Promise<T>,
maxRetries = 3,
baseDelay = 1000
): Promise<T> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn()
} catch (err) {
// Don't retry client errors (bad input won't fix itself)
if (err.status >= 400 && err.status < 500 && err.status !== 429) {
throw err
}
if (attempt === maxRetries) throw err
// Exponential backoff: 1s, 2s, 4s
const delay = baseDelay * Math.pow(2, attempt)
await new Promise(resolve => setTimeout(resolve, delay))
}
}
throw new Error('Unreachable')
}
// Usage
const users = await withRetry(() =>
sdk.db.query('SELECT * FROM users WHERE active = 1')
)Retry with rate limit awareness
async function withRateLimitRetry<T>(fn: () => Promise<T>): Promise<T> {
try {
return await fn()
} catch (err) {
if (err.code === 'RATE_LIMIT_EXCEEDED' && err.details?.retryAfter) {
// Wait the exact time the server tells us
await new Promise(resolve =>
setTimeout(resolve, err.details.retryAfter * 1000)
)
return await fn()
}
throw err
}
}Token refresh on auth error
The SDK handles token refresh automatically on 401 responses. For manual handling:
async function authenticatedRequest<T>(fn: () => Promise<T>): Promise<T> {
try {
return await fn()
} catch (err) {
if (err.code === 'AUTH_INVALID_TOKEN') {
// Token expired -- refresh and retry
await refreshAccessToken(tokens.refreshToken)
return await fn()
}
throw err
}
}React error boundaries
For unexpected errors, wrap SDK-dependent components in an error boundary:
import { Component, ReactNode } from 'react'
interface Props {
children: ReactNode
fallback: ReactNode
}
interface State {
hasError: boolean
error: Error | null
}
export class SDKErrorBoundary extends Component<Props, State> {
state: State = { hasError: false, error: null }
static getDerivedStateFromError(error: Error) {
return { hasError: true, error }
}
render() {
if (this.state.hasError) {
return this.props.fallback
}
return this.props.children
}
}
// Usage
<SDKErrorBoundary fallback={<p>Something went wrong. Please reload.</p>}>
<LiveDashboard />
</SDKErrorBoundary>Debugging tips
-
Check the
errorstate — React hooks (useAuth,useDb) set anerrorstring automatically. Display it in your UI during development. -
Log error details — The
detailsproperty often contains field-level validation errors or rate limit retry-after values. -
Check the network tab — SDK errors include the HTTP status code and response body. The browser network tab shows the exact request and response.
-
Enable verbose logging — Set the
debugoption when initializing the SDK:
const client = new AerostackClient({
projectId,
apiKey,
debug: true, // Logs all requests and responses to console
})- Check the dashboard — Error logs are available in the Aerostack Dashboard under your project’s Logs section.