# Error Handling

> Error types, try/catch patterns, and retry strategies for the Aerostack SDK.

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:

```ts
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

  
```tsx

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>
      
      {error && <p style={{ color: 'red' }}>{error}</p>}
    </div>
  )
}
```
  
  
```ts

const { sdk } = new AerostackClient({ projectId, apiKey })

try {
  const { results } = await sdk.db.query('SELECT * FROM users WHERE id = ?', [userId])
} catch (err) {
  if (err instanceof AuthenticationError) {
    // Token expired or invalid -- refresh and retry
  } else if (err instanceof ValidationError) {
    // Bad input -- check err.details for field-level errors
    console.error('Validation failed:', err.details)
  } else if (err instanceof RateLimitError) {
    // Too many requests -- wait and retry
    const retryAfter = err.details?.retryAfter ?? 60
    console.error(`Rate limited. Retry after ${retryAfter}s`)
  } else {
    // Unknown error
    console.error('Unexpected error:', err.message)
  }
}
```
  

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

```ts
async function withRetry(
  fn: () => Promise,
  maxRetries = 3,
  baseDelay = 1000
): Promise {
  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

```ts
async function withRateLimitRetry(fn: () => Promise): Promise {
  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:

```ts
async function authenticatedRequest(fn: () => Promise): Promise {
  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:

```tsx title="src/components/ErrorBoundary.tsx"

interface Props {
  children: ReactNode
  fallback: ReactNode
}

interface State {
  hasError: boolean
  error: Error | null
}

  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
Something went wrong. Please reload.</p>}>
  

```

---

## Debugging tips

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:

```ts
const client = new AerostackClient({
  projectId,
  apiKey,
  debug: true, // Logs all requests and responses to console
})
```

5. **Check the dashboard** -- Error logs are available in the Aerostack Dashboard under your project's Logs section.
