> ## Documentation Index
> Fetch the complete documentation index at: https://docs.flowsbuilt.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> ChurnKitError — structured errors with typed codes for exhaustive handling.

## ChurnKitError

All SDK methods throw `ChurnKitError` on failure. It extends `Error` and adds `status` (HTTP status code) and `code` (typed error constant).

```typescript theme={null}
import { ChurnKitError, ErrorCode } from '@vgpprasad91/churnkit-sdk'

try {
  await churn.score('user_123')
} catch (err) {
  if (err instanceof ChurnKitError) {
    console.log(err.message)  // human-readable description
    console.log(err.status)   // HTTP status code (or undefined for network errors)
    console.log(err.code)     // 'TIMEOUT' | 'UNAUTHORIZED' | etc.
  }
}
```

## Error codes

| Code                | HTTP | When                                                      |
| ------------------- | ---- | --------------------------------------------------------- |
| `ABORTED`           | —    | The caller's `AbortSignal` fired                          |
| `TIMEOUT`           | —    | Request exceeded configured timeout                       |
| `NETWORK_ERROR`     | —    | DNS failure, connection refused, etc.                     |
| `VALIDATION_ERROR`  | 400  | Invalid input (empty userId, bad threshold, etc.)         |
| `UNAUTHORIZED`      | 401  | API key is missing, invalid, or revoked                   |
| `FORBIDDEN`         | 403  | API key lacks permission for this operation               |
| `NOT_FOUND`         | 404  | Resource doesn't exist (user not tracked, no watch, etc.) |
| `RATE_LIMITED`      | 429  | Too many requests — back off and retry                    |
| `SERVER_ERROR`      | 5xx  | ChurnKit server error                                     |
| `PAYLOAD_TOO_LARGE` | 413  | `bulkEvent()` exceeded 500 items                          |

## Handling patterns

### Ignore cancellations

```typescript theme={null}
try {
  const risk = await churn.score(userId, { signal })
  return risk
} catch (err) {
  if (err instanceof ChurnKitError && err.code === ErrorCode.ABORTED) {
    return null // user navigated away — ignore
  }
  throw err
}
```

### Exhaustive switch with type narrowing

```typescript theme={null}
import { ChurnKitError, ErrorCode, isKnownErrorCode } from '@vgpprasad91/churnkit-sdk'

try {
  await churn.identify(userId, traits)
} catch (err) {
  if (err instanceof ChurnKitError && isKnownErrorCode(err.code)) {
    switch (err.code) {
      case ErrorCode.UNAUTHORIZED:
        logger.error('ChurnKit API key invalid — check CHURNKIT_API_KEY')
        break
      case ErrorCode.RATE_LIMITED:
        logger.warn('ChurnKit rate limited — consider batching')
        break
      case ErrorCode.TIMEOUT:
        logger.warn('ChurnKit timeout — request took too long')
        break
      case ErrorCode.VALIDATION_ERROR:
        logger.error('ChurnKit validation error', err.message)
        break
      default:
        logger.error('ChurnKit error', err.code, err.message)
    }
  }
}
```

### Non-blocking with error logging

```typescript theme={null}
async function trackSafe(userId: string, event: string) {
  try {
    await churn.event(userId, event)
  } catch (err) {
    // Never let tracking failures break the main flow
    if (err instanceof ChurnKitError) {
      logger.warn('ChurnKit track failed', { code: err.code, event })
    }
  }
}
```

## isKnownErrorCode()

Type guard that narrows `err.code` to a well-known `ErrorCode` constant — useful for exhaustive switch statements.

```typescript theme={null}
import { isKnownErrorCode } from '@vgpprasad91/churnkit-sdk'

if (err instanceof ChurnKitError && isKnownErrorCode(err.code)) {
  // err.code is now typed as ErrorCode — IDE autocomplete works
  switch (err.code) { ... }
}
```
