# Error handling

> Typed error classes, chat error codes, and the SSE stall watchdog.

Source: https://maincode.com/docs/client-sdk-error-handling
Section: Client SDK · Matilda documentation

---

## `MatildaAPIError`

Thrown on non-2xx HTTP responses from the API.

```ts
class MatildaAPIError extends Error {
  readonly status: number;       // HTTP status code
  readonly responseText: string; // Raw response body
}
```

## `SafetyReplaceError`

Thrown by `chat.streamText()`, `chat.streamObject()`, and `chat.createObject()` when the server replaces the output via a safety filter. The `message` property contains the replacement text (or empty string), and `categories` lists the safety categories that triggered the replacement.

```ts
class SafetyReplaceError extends Error {
  readonly categories: string[];
}
```

## `MatildaObjectParseError`

Thrown by `chat.streamObject()` and `chat.createObject()` when the response cannot be parsed as JSON or fails zod validation — e.g. when a stream is truncated. `raw` contains the full response text; `cause` is the underlying `JSON.parse` or zod error. See [Structured output](https://maincode.com/docs/client-sdk-structured-output).

```ts
class MatildaObjectParseError extends Error {
  readonly raw: string;
  readonly cause: unknown;
}
```

## `AuthError`

Thrown by auth flows. The `code` field is an OAuth error code (e.g. `'invalid_grant'`, `'authorization_pending'`, `'expired_token'`, `'access_denied'`). The `retryable` field distinguishes transient failures (5xx, network) from permanent ones (revoked refresh token).

```ts
class AuthError extends Error {
  readonly code: string;
  readonly retryable: boolean;
}
```

## Chat error codes (`ChatErrorCode`)

These codes are emitted via the `response.error` stream event and appear in `MatildaChatResponse.errors`:

| Code | Description |
| - | - |
| `internal_error` | Server-side failure. |
| `upstream_unavailable` | The AI model is not responding. |
| `rate_limited` | Too many requests. |
| `content_blocked` | Safety filter blocked the content. |
| `stream_aborted` | The stream was interrupted before completion. |
| `deadline_exceeded` | The response did not finish before the deadline. |
| `context_too_large` | The conversation is too long for the model. |
| `stalled` | No SSE events for the configured stall window. |
| `stream_expired` | The durable stream buffer expired (resume path only). |
| `unknown` | Unclassified error (old server without typed codes). |

## Stall watchdog

The streaming parser arms an idle-event watchdog. If no SSE event arrives for `stallTimeoutMs` milliseconds, the stream is considered dead and aborted with a `'stalled'` error.

- **Default:** `45_000` ms (3× the server's 15-second keep-alive ping cadence)
- **Disable:** Pass `stallTimeoutMs: 0` in `MatildaRequestOptions` (not recommended — mobile loses background-to-foreground hung-stream recovery)

## Error handling example

```ts title="errors.ts"
import Matilda, { MatildaAPIError, SafetyReplaceError } from '@maincode-ai/matilda-client-sdk';

try {
  const text = await client.chat.createText({ input: 'Hello!' });
  console.log(text);
} catch (err) {
  if (err instanceof MatildaAPIError) {
    if (err.status === 401) {
      console.error('Session expired — re-authenticate.');
    } else if (err.status === 429) {
      console.error('Rate limited — slow down.');
    } else {
      console.error(`API error ${err.status}: ${err.responseText}`);
    }
  } else if (err instanceof SafetyReplaceError) {
    console.error(`Safety filter: ${err.categories.join(', ')}`);
  } else {
    console.error('Unexpected error:', err);
  }
}
```
