Skip to content

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.

The SDK throws specific error classes depending on what went wrong:

Error ClassWhen it occursHTTP Status
ClientErrorInvalid request, missing parameters, bad input400
AuthenticationErrorInvalid or expired token, unauthorized access401
ForbiddenErrorValid token but insufficient permissions403
NotFoundErrorResource does not exist404
ValidationErrorInput fails server-side validation rules422
RateLimitErrorToo many requests429
ServerErrorUnexpected server failure500+
NetworkErrorConnection failed, timeout, DNS failureN/A

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)
}
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>
)
}
CodeDescription
AUTH_INVALID_CREDENTIALSWrong email or password
AUTH_INVALID_TOKENAccess token is malformed or expired
AUTH_ACCOUNT_LOCKEDAccount locked due to too many failed attempts (60-minute lockout)
AUTH_EMAIL_NOT_VERIFIEDEmail verification required before sign-in
AUTH_USER_NOT_FOUNDNo account with this email
AUTH_EMAIL_ALREADY_EXISTSEmail already registered
AUTH_OTP_EXPIREDOTP code has expired (10-minute window)
AUTH_OTP_INVALIDWrong OTP code
AUTH_REFRESH_TOKEN_INVALIDRefresh token expired or revoked
CodeDescription
DB_QUERY_ERRORSQL syntax error or constraint violation
DB_CONSTRAINT_VIOLATIONUnique constraint, foreign key, or NOT NULL violation
DB_TABLE_NOT_FOUNDReferenced table does not exist
CodeDescription
STORAGE_FILE_TOO_LARGEUpload exceeds size limit
STORAGE_FILE_NOT_FOUNDRequested file does not exist
STORAGE_QUOTA_EXCEEDEDStorage quota for the project is full
CodeDescription
RATE_LIMIT_EXCEEDEDToo many requests in the time window
AUTH_OTP_VERIFY_LIMITToo many OTP verification attempts (max 3)

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')
)
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
}
}

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
}
}

For unexpected errors, wrap SDK-dependent components in an error boundary:

src/components/ErrorBoundary.tsx
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>}>
{/* Your SDK-dependent components go here */}
</SDKErrorBoundary>

  1. Check the error state — React hooks (useAuth, useDb) set an error string automatically. Display it in your UI during development.

  2. Log error details — The details property often contains field-level validation errors or rate limit retry-after values.

  3. Check the network tab — SDK errors include the HTTP status code and response body. The browser network tab shows the exact request and response.

  4. Enable verbose logging — Set the debug option when initializing the SDK:

const client = new AerostackClient({
projectId,
apiKey,
debug: true, // Logs all requests and responses to console
})
  1. Check the dashboard — Error logs are available in the Aerostack Dashboard under your project’s Logs section.