---
title: Error Handling
description: Handle rate limits, unsupported features, and other errors from adapters.
type: guide
prerequisites:
  - /docs/usage
---

# Error Handling



The SDK provides typed error classes for common failure scenarios. All errors are importable from the `chat` package.

```typescript
import { ChatError, RateLimitError, NotImplementedError, LockError } from "chat";
```

## Error types

### ChatError

Base error class for all SDK errors. Every error below extends `ChatError`. The `code` property carries a machine-readable identifier you can branch on:

| Code                     | Thrown by                      | Meaning                                                                                |
| ------------------------ | ------------------------------ | -------------------------------------------------------------------------------------- |
| `NOT_SUPPORTED`          | `bot.openDM`, `bot.getUser`    | The resolved adapter doesn't implement this method                                     |
| `INVALID_THREAD_ID`      | `bot.thread`, internal routing | Thread ID does not match the `adapter:channel:thread` shape                            |
| `INVALID_CHANNEL_ID`     | `bot.channel`                  | Channel ID does not match the `adapter:channel` shape                                  |
| `ADAPTER_NOT_FOUND`      | `bot.thread`, `bot.channel`    | Thread/channel ID references an adapter that wasn't registered on this `Chat` instance |
| `AMBIGUOUS_USER_ID`      | `bot.getUser`, `bot.openDM`    | Numeric user ID could match more than one registered adapter (Discord/Telegram/GitHub) |
| `UNKNOWN_USER_ID_FORMAT` | `bot.getUser`, `bot.openDM`    | The `userId` doesn't match any platform's known ID format                              |
| `RATE_LIMITED`           | Any platform call              | Platform returned 429; see `RateLimitError` below                                      |
| `NOT_IMPLEMENTED`        | Any platform call              | The adapter doesn't implement this feature; see `NotImplementedError` below            |
| `LOCK_FAILED`            | Inbound message routing        | Distributed lock was busy; see `LockError` below                                       |

<TypeTable
  type={{
  message: {
    description: 'Human-readable error message.',
    type: 'string',
  },
  code: {
    description: 'Machine-readable error code (see table above).',
    type: 'string',
  },
  cause: {
    description: 'Original error that caused this one.',
    type: 'unknown | undefined',
  },
}}
/>

### RateLimitError

Thrown when a platform API returns a 429 response. The `retryAfterMs` property tells you how long to wait before retrying.

```typescript title="lib/bot.ts" lineNumbers
import { RateLimitError } from "chat";

try {
  await thread.post("Hello!");
} catch (error) {
  if (error instanceof RateLimitError) {
    console.log(`Rate limited, retry after ${error.retryAfterMs}ms`);
  }
}
```

<TypeTable
  type={{
  code: {
    description: 'Always "RATE_LIMITED".',
    type: 'string',
  },
  retryAfterMs: {
    description: 'Milliseconds to wait before retrying.',
    type: 'number | undefined',
  },
}}
/>

### NotImplementedError

Thrown when you call a feature that a platform doesn't support. For example, calling `addReaction()` on Teams or `schedule()` on adapters without native scheduling support.

```typescript title="lib/bot.ts" lineNumbers
import { NotImplementedError } from "chat";

try {
  await thread.addReaction(emoji.thumbs_up);
} catch (error) {
  if (error instanceof NotImplementedError) {
    console.log(`Feature not supported: ${error.feature}`);
  }
}
```

<TypeTable
  type={{
  code: {
    description: 'Always "NOT_IMPLEMENTED".',
    type: 'string',
  },
  feature: {
    description: 'Name of the unsupported feature.',
    type: 'string | undefined',
  },
}}
/>

See the [feature matrix](/docs/adapters) for which features are supported on each platform.

### LockError

Thrown when the SDK fails to acquire a distributed lock on a thread (used to prevent concurrent processing of messages in the same thread). You can control this behavior with the [`onLockConflict`](/docs/usage#configuration-options) option — set it to `'force'` to release the existing lock instead of throwing.

<TypeTable
  type={{
  code: {
    description: 'Always "LOCK_FAILED".',
    type: 'string',
  },
}}
/>

## Adapter errors

Adapters also throw specialized errors from the `@chat-adapter/shared` package:

| Error                   | Code                | Description                                               |
| ----------------------- | ------------------- | --------------------------------------------------------- |
| `AdapterRateLimitError` | `RATE_LIMITED`      | Platform rate limit hit, includes `retryAfter` in seconds |
| `AuthenticationError`   | `AUTH_FAILED`       | Invalid or expired credentials                            |
| `ResourceNotFoundError` | `NOT_FOUND`         | Requested resource (channel, message) doesn't exist       |
| `PermissionError`       | `PERMISSION_DENIED` | Bot lacks required permissions/scopes                     |
| `ValidationError`       | `VALIDATION_ERROR`  | Invalid input data (e.g. message too long)                |
| `NetworkError`          | `NETWORK_ERROR`     | Connectivity issue with platform API                      |

## Catching errors

Use `instanceof` to handle specific error types:

```typescript title="lib/bot.ts" lineNumbers
import { RateLimitError, NotImplementedError } from "chat";

bot.onNewMention(async (thread, message) => {
  try {
    await thread.post("Processing...");
    await thread.addReaction(emoji.eyes);
  } catch (error) {
    if (error instanceof RateLimitError) {
      // Wait and retry
      await new Promise((r) => setTimeout(r, error.retryAfterMs ?? 5000));
      await thread.post("Processing...");
    } else if (error instanceof NotImplementedError) {
      // Skip unsupported features gracefully
    } else {
      throw error;
    }
  }
});
```
