# Error handling

> Every error class exported by the TypeScript client, its status and error code, when it is thrown, and how to handle it.

Source: https://10xgraph.com/docs/reference/client/errors
Last updated: 2026-10-08

When the server returns a non-2xx response, `TenxGraphClient` throws an `TenxGraphError` or one of its 14 subclasses. The class is chosen from the response's error code first, then from the HTTP status. Network failures and client timeouts are not API errors: they throw a plain `Error`.

**Import:** `import { TenxGraphError } from '10xgraph-client';`

For the practical guide (retries, stream errors), see [Handle errors](/docs/client/error-handling). For the codes the server can return, see [Error codes](/docs/reference/error-codes).

## How the client picks an error class

The client reads the JSON error body, then matches the error `code` by prefix, then falls back to the HTTP status. A `GRAPH_RECURSION_ERROR` returned with status 500 therefore arrives as `GraphRecursionError`, not `ServerError`.

| Order | Match | Class |
|---|---|---|
| 1 | Code starts with `GRAPH_RECURSION` | `GraphRecursionError` |
| 2 | Code starts with `GRAPH` | `GraphError` |
| 3 | Code starts with `NODE` | `NodeError` |
| 4 | Code starts with `TRANSIENT_STORAGE` | `TransientStorageError` |
| 5 | Code starts with `STORAGE` | `StorageError` |
| 6 | Code starts with `METRICS` | `MetricsError` |
| 7 | Code starts with `SCHEMA_VERSION` | `SchemaVersionError` |
| 8 | Code starts with `SERIALIZATION` | `SerializationError` |
| 9 | Status 400, 401, 403, 404, 422 | `BadRequestError`, `AuthenticationError`, `PermissionError`, `NotFoundError`, `ValidationError` |
| 10 | Status 500, 502, 503, 504 | `ServerError` |
| 11 | Any other status | `TenxGraphError` with code `UNKNOWN_ERROR` (unless the body gave one) |

If the body is not JSON or cannot be parsed, the same status mapping applies with `requestId` set to `'unknown'`.

> **Subclass-only fields are not filled by the client**
>
> `context`, `nodeName`, `recursionLimit`, `expectedVersion` and `actualVersion` exist on the classes, but the client's own response parsing never sets them. They are `undefined` on errors the client throws; they are set only if you construct these classes yourself.

## TenxGraphError (base class)

`TenxGraphError` extends the built-in `Error`; every other class on this page extends it. Check it last in an `instanceof` chain, after the specific subclasses.

| Property | Type | Description |
|---|---|---|
| `message` | `string` | Server error message, or the fallback message. |
| `statusCode` | `number` | HTTP status code. |
| `errorCode` | `string` | Machine-readable code, e.g. `VALIDATION_ERROR`. |
| `requestId` | `string` | `metadata.request_id` from the response, or `'unknown'`. |
| `timestamp` | `string` | `metadata.timestamp`, or the client's ISO 8601 time when missing. |
| `details` | `ErrorDetail[]` | Field-level details from the server. Empty array by default. |
| `context` | `Record<string, any> \| undefined` | Extra context (see the note above). |
| `endpoint` | `string \| undefined` | API path. Set only for unmapped status codes, and only if passed in. |
| `method` | `string \| undefined` | HTTP method. Same condition as `endpoint`. |
| `recoverySuggestion` | `string \| undefined` | Hint on how to fix the error. Set by the graph, node, storage, metrics, schema and serialization classes. |

Two methods are available on every error:

| Method | Returns | Description |
|---|---|---|
| `getUserMessage()` | `string` | `message`, plus `"\n\nSuggestion: ..."` when `recoverySuggestion` is set. |
| `toJSON()` | `Record<string, any>` | `name`, `message`, `statusCode`, `errorCode`, `requestId`, `timestamp`, `details`, `context`, `endpoint`, `method`, `recoverySuggestion` and `stack`. |

`toJSON()` includes the stack trace, so log it on the server but do not send it to end users.

```ts title="log-error.ts"
import { TenxGraphClient, TenxGraphError, Message } from '10xgraph-client';

const client = new TenxGraphClient({ baseUrl: 'http://localhost:8000' });

try {
  await client.invoke([Message.text_message('Hi')]);
} catch (err) {
  if (err instanceof TenxGraphError) {
    // Log structured details, show a safe message to the user.
    console.error(`Error ${err.statusCode} (${err.errorCode}): ${err.getUserMessage()}`);
    console.error(JSON.stringify(err.toJSON()));
  } else {
    throw err; // network failure or client-side timeout
  }
}
```

## HTTP status errors

These five classes map to one status each. They carry no recovery suggestion, so read `message` and `details`.

| Class | Status | Error code | Thrown when |
|---|---|---|---|
| `BadRequestError` | 400 | `BAD_REQUEST` | The server rejects the request as malformed. |
| `AuthenticationError` | 401 | `AUTHENTICATION_FAILED` | Credentials are missing, invalid or expired. |
| `PermissionError` | 403 | `PERMISSION_ERROR` | The caller is authenticated but not allowed to perform the action. |
| `NotFoundError` | 404 | `RESOURCE_NOT_FOUND` | The thread, message, memory or file does not exist. |
| `ValidationError` | 422 | `VALIDATION_ERROR` | The request body fails schema validation. `details` lists the failing fields. |

```ts title="http-status-errors.ts"
import {
  TenxGraphClient,
  AuthenticationError,
  NotFoundError,
  ValidationError,
} from '10xgraph-client';

const client = new TenxGraphClient({ baseUrl: 'http://localhost:8000' });

try {
  await client.threadDetails('thread-123');
} catch (err) {
  if (err instanceof AuthenticationError) {
    window.location.href = '/login'; // token missing or expired
  } else if (err instanceof NotFoundError) {
    console.error('Thread not found.');
  } else if (err instanceof ValidationError) {
    // Each detail is { loc?, msg?, type? }.
    for (const detail of err.details) {
      console.error(`Field ${detail.loc?.join('.')}: ${detail.msg}`);
    }
  } else {
    throw err;
  }
}
```

### ServerError

`ServerError` covers status 500, 502, 503 and 504 when the error code does not match a more specific class. Its `errorCode` is the server's code, or `INTERNAL_SERVER_ERROR` when absent, and `statusCode` is the actual status.

Treat 502, 503 and 504 as usually transient and retry them with backoff. Do not retry a 500 blindly: it is often a bug in a node or tool.

```ts title="server-error.ts"
import { TenxGraphClient, Message, ServerError } from '10xgraph-client';

const client = new TenxGraphClient({ baseUrl: 'http://localhost:8000' });

try {
  await client.invoke([Message.text_message('Hi')]);
} catch (err) {
  if (err instanceof ServerError && [502, 503, 504].includes(err.statusCode)) {
    console.warn('Gateway or availability problem, retry later.');
  } else {
    throw err;
  }
}
```

## Graph and node errors

These classes mean the graph failed while running. Each has status 500 and its own recovery suggestion.

| Class | Error code | Extra property | Recovery suggestion |
|---|---|---|---|
| `GraphError` | `GRAPH_ERROR` | none | Check your graph configuration and ensure all nodes are properly connected. |
| `NodeError` | `NODE_ERROR` | `nodeName?: string` | Review the node implementation and ensure all required inputs are provided. |
| `GraphRecursionError` | `GRAPH_RECURSION_ERROR` | `recursionLimit?: number` | Consider increasing the recursion_limit parameter or check for infinite loops in your graph. |

`GraphRecursionError` means a run exceeded its step limit. `invoke` sends `recursion_limit` (default 25) with each request, so raise it in the options if the graph legitimately needs more steps.

```ts title="graph-errors.ts"
import { TenxGraphClient, GraphRecursionError, Message, NodeError } from '10xgraph-client';

const client = new TenxGraphClient({ baseUrl: 'http://localhost:8000' });

try {
  await client.invoke([Message.text_message('Plan my trip')], { recursion_limit: 50 });
} catch (err) {
  if (err instanceof GraphRecursionError) {
    console.error('Run exceeded its step limit. Check the graph for loops.');
  } else if (err instanceof NodeError) {
    console.error('A node failed:', err.message);
  } else {
    throw err;
  }
}
```

## Storage errors

Storage errors mean the checkpointer or memory store failed. The two classes differ in whether a retry helps.

| Class | Status | Error code | Retry | Recovery suggestion |
|---|---|---|---|---|
| `StorageError` | 500 | `STORAGE_ERROR` | No | Check your storage configuration and ensure the storage backend is accessible. |
| `TransientStorageError` | 503 | `TRANSIENT_STORAGE_ERROR` | Yes, with backoff | This is a temporary issue. Please retry your request after a short delay. |

`TransientStorageError` is matched before `StorageError` because its code starts with `TRANSIENT_STORAGE`.

```ts title="storage-errors.ts"
import { TenxGraphClient, StorageError, TransientStorageError } from '10xgraph-client';

const client = new TenxGraphClient({ baseUrl: 'http://localhost:8000' });

async function loadState(threadId: string) {
  try {
    return await client.threadState(threadId);
  } catch (err) {
    if (err instanceof TransientStorageError) {
      await new Promise((resolve) => setTimeout(resolve, 1000));
      return client.threadState(threadId); // one retry after a short delay
    }
    if (err instanceof StorageError) {
      console.error('Storage backend is unavailable:', err.message);
    }
    throw err;
  }
}
```

## Serialization, schema and metrics errors

These three classes cover data format, version and telemetry failures.

| Class | Status | Error code | Thrown when | Recovery suggestion |
|---|---|---|---|---|
| `SerializationError` | 500 | `SERIALIZATION_ERROR` | A payload cannot be serialized or deserialized. | Ensure your data format is compatible with the API schema. |
| `SchemaVersionError` | 422 | `SCHEMA_VERSION_ERROR` | Client and server schema versions disagree. Has `expectedVersion?` and `actualVersion?` (`string`). | Update your client to match the server schema version or contact support. |
| `MetricsError` | 500 | `METRICS_ERROR` | Metrics collection or reporting failed. | Check your metrics configuration. This error typically doesn't affect core functionality. |

`SchemaVersionError` shares status 422 with `ValidationError`; the code prefix is what separates them. A `MetricsError` may mean the operation itself worked, so log it and decide per call whether to treat it as fatal.

```ts title="schema-errors.ts"
import { TenxGraphClient, MetricsError, SchemaVersionError } from '10xgraph-client';

const client = new TenxGraphClient({ baseUrl: 'http://localhost:8000' });

try {
  await client.updateThreadState('thread-1', {}, { context: [] });
} catch (err) {
  if (err instanceof SchemaVersionError) {
    console.error('Client and server schemas differ. Update the client.');
  } else if (err instanceof MetricsError) {
    console.warn('Metrics failed; the request may have succeeded.');
  } else {
    throw err;
  }
}
```

## A complete recovery strategy

Check specific subclasses first and the base class last. This handler routes each failure to the right response: log in again, show field errors, retry once, or surface a safe message.

```ts title="handle-errors.ts"
import {
  TenxGraphClient,
  TenxGraphError,
  AuthenticationError,
  GraphRecursionError,
  Message,
  TransientStorageError,
  ValidationError,
} from '10xgraph-client';

const client = new TenxGraphClient({ baseUrl: 'http://localhost:8000' });
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

async function ask(text: string, attempt = 0): Promise<unknown> {
  try {
    return await client.invoke([Message.text_message(text)]);
  } catch (err) {
    if (err instanceof AuthenticationError) {
      window.location.href = '/login';
    } else if (err instanceof ValidationError) {
      console.error('Invalid request:', err.details);
    } else if (err instanceof TransientStorageError && attempt < 3) {
      await sleep(1000 * 2 ** attempt); // exponential backoff
      return ask(text, attempt + 1);
    } else if (err instanceof GraphRecursionError) {
      console.error('Step limit exceeded. Raise recursion_limit or fix the loop.');
    } else if (err instanceof TenxGraphError) {
      console.error(err.getUserMessage());
    } else {
      throw err; // network error or timeout
    }
  }
}

await ask('Hello');
```

## Build errors in your own proxy

`parseErrorResponse` and `createErrorFromResponse` are exported for code that calls the API itself, for example a server route that proxies requests. Use them to get the same error classes the client produces.

| Function | Parameters | Returns |
|---|---|---|
| `parseErrorResponse` | `response: Response` | `Promise<ApiErrorResponse \| null>`. `null` if the content type is not JSON or parsing fails. |
| `createErrorFromResponse` | `response: Response`, `fallbackMessage?: string`, `endpoint?: string`, `method?: string` | `Promise<TenxGraphError>`, the most specific subclass. |

```ts title="app/api/invoke/route.ts"
import { createErrorFromResponse } from '10xgraph-client';

export async function POST(req: Request) {
  const upstream = await fetch('http://localhost:8000/v1/graph/invoke', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: await req.text(),
  });

  if (!upstream.ok) {
    const error = await createErrorFromResponse(
      upstream,
      'Invoke failed',
      '/v1/graph/invoke',
      'POST'
    );
    // Return a safe subset. toJSON() includes the stack trace.
    return Response.json(
      { code: error.errorCode, message: error.message, requestId: error.requestId },
      { status: error.statusCode }
    );
  }

  return Response.json(await upstream.json());
}
```

## Response shapes

These exported types describe the error body the server sends and the field-level detail entries.

```ts
interface ErrorDetail {
  loc?: string[]; // field path, e.g. ['body', 'messages']
  msg?: string;   // human-readable message
  type?: string;  // failure type, e.g. 'value_error'
}

interface ApiErrorResponse {
  metadata: { message: string; request_id: string; timestamp: string };
  error: { code: string; message: string; details: ErrorDetail[] };
}
```

## Related pages

- [Handle errors](/docs/client/error-handling): retries, error blocks and error chunks in streams.
- [Create a client](/docs/client/create-client): configuration, auth and timeout.
- [Error codes](/docs/reference/error-codes): the codes the server returns.
