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

# TypeScript Error Handling

> Handle errors and retries with the DevHelm TypeScript SDK

The SDK throws typed errors for API failures, validation issues, and network problems. The taxonomy mirrors the API's `ErrorResponse` envelope, with subclasses per HTTP class for ergonomic catching.

## Error class hierarchy

```
DevhelmError                  ← umbrella; catch as a fallback
├── DevhelmValidationError    ← local request/response shape validation
├── DevhelmTransportError     ← connection refused, DNS, TLS, timeout
└── DevhelmApiError           ← non-2xx HTTP response
    ├── DevhelmAuthError      ← 401, 403
    ├── DevhelmNotFoundError  ← 404
    ├── DevhelmConflictError  ← 409
    ├── DevhelmRateLimitError ← 429
    └── DevhelmServerError    ← 5xx
```

All classes are importable from the package root:

```typescript theme={null}
import {
  DevhelmError,
  DevhelmValidationError,
  DevhelmApiError,
  DevhelmAuthError,
  DevhelmNotFoundError,
  DevhelmConflictError,
  DevhelmRateLimitError,
  DevhelmServerError,
  DevhelmTransportError,
} from "@devhelm/sdk";
```

## DevhelmApiError

The most common error class — thrown for any non-2xx HTTP response.

| Property    | Type                         | Description                                                                                                                                 |
| ----------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `status`    | `number`                     | HTTP status code (e.g. `404`)                                                                                                               |
| `code`      | `string`                     | Coarse machine-readable category from `ErrorResponse.code` (e.g. `"NOT_FOUND"`). Defaults to `"API_ERROR"` if the server didn't supply one. |
| `message`   | `string`                     | Human-readable message — for logs and user-facing copy                                                                                      |
| `detail`    | `string \| undefined`        | Additional context, when available                                                                                                          |
| `body`      | `ErrorResponse \| undefined` | Parsed canonical error envelope if the body matched the contract                                                                            |
| `rawBody`   | `unknown`                    | Raw response body for debugging non-conforming responses                                                                                    |
| `requestId` | `string \| undefined`        | Per-request id from the `X-Request-Id` header. **Always include in support tickets.**                                                       |

```typescript theme={null}
import { DevhelmApiError } from "@devhelm/sdk";

try {
  await client.monitors.get("missing");
} catch (err) {
  if (err instanceof DevhelmApiError) {
    console.error(err.status);    // 404
    console.error(err.code);      // "NOT_FOUND"
    console.error(err.requestId); // "req_01HJV..."
  }
}
```

## DevhelmValidationError

Thrown before HTTP I/O when a request body fails the Zod schema, and after a successful response when the response body doesn't match the expected schema (rare — usually indicates a stale SDK).

| Property  | Type                             | Description                                          |
| --------- | -------------------------------- | ---------------------------------------------------- |
| `message` | `string`                         | Summary of the first 5 issues                        |
| `issues`  | `ReadonlyArray<ValidationIssue>` | Structured issues with `path`, `message`, and `code` |

```typescript theme={null}
import { DevhelmValidationError } from "@devhelm/sdk";

try {
  await client.monitors.create({ type: "HTTP" } as any); // missing fields
} catch (err) {
  if (err instanceof DevhelmValidationError) {
    for (const issue of err.issues) {
      console.error(`${issue.path.join(".")}: ${issue.message}`);
    }
  }
}
```

## DevhelmTransportError

Thrown for network-level failures (connection refused, DNS, TLS handshake, timeout). The original `fetch` error is on `.cause`.

```typescript theme={null}
import { DevhelmTransportError } from "@devhelm/sdk";

try {
  await client.monitors.list();
} catch (err) {
  if (err instanceof DevhelmTransportError) {
    console.error("Network error:", err.message);
    console.error("Underlying:", err.cause);
  }
}
```

## Handling patterns

### Catch by HTTP class

```typescript theme={null}
import {
  DevhelmAuthError,
  DevhelmNotFoundError,
  DevhelmRateLimitError,
  DevhelmError,
} from "@devhelm/sdk";

try {
  const monitor = await client.monitors.get(monitorId);
} catch (err) {
  if (err instanceof DevhelmAuthError) {
    console.error("Bad token or insufficient permissions");
  } else if (err instanceof DevhelmNotFoundError) {
    console.error(`Monitor ${monitorId} no longer exists`);
  } else if (err instanceof DevhelmRateLimitError) {
    console.error(`Rate limited (request_id=${err.requestId})`);
  } else if (err instanceof DevhelmError) {
    throw err;
  }
}
```

### Retry on rate limits and 5xx

```typescript theme={null}
import {
  DevhelmRateLimitError,
  DevhelmServerError,
  DevhelmTransportError,
} from "@devhelm/sdk";

async function withRetry<T>(
  fn: () => Promise<T>,
  { maxRetries = 3, baseDelayMs = 1000 } = {},
): Promise<T> {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (err) {
      const retryable =
        err instanceof DevhelmRateLimitError ||
        err instanceof DevhelmServerError ||
        err instanceof DevhelmTransportError;
      if (!retryable || attempt === maxRetries) throw err;
      const delay = baseDelayMs * Math.pow(2, attempt);
      await new Promise((r) => setTimeout(r, delay));
    }
  }
  throw new Error("unreachable");
}

const monitors = await withRetry(() => client.monitors.list());
```

<Note>
  Don't retry on `DevhelmValidationError`, `DevhelmAuthError`, `DevhelmNotFoundError`, or `DevhelmConflictError` — these are deterministic and won't change without code or config changes.
</Note>

### Deploy lock contention

```typescript theme={null}
import { DevhelmConflictError } from "@devhelm/sdk";

try {
  const lock = await client.deployLock.acquire({
    lockedBy: "ci-pipeline",
    ttlMinutes: 30,
  });
} catch (err) {
  if (err instanceof DevhelmConflictError) {
    const current = await client.deployLock.current();
    console.error(`Lock held by ${current?.lockedBy} (since ${current?.lockedAt})`);
  }
}
```

### Catch-all logging

```typescript theme={null}
import { DevhelmApiError, DevhelmError } from "@devhelm/sdk";

try {
  await doWork(client);
} catch (err) {
  if (err instanceof DevhelmApiError) {
    log.error("DevHelm API error", {
      status: err.status,
      code: err.code,
      requestId: err.requestId,
      detail: err.detail,
    });
  } else if (err instanceof DevhelmError) {
    log.error("DevHelm SDK error", { message: err.message });
  } else {
    throw err;
  }
}
```

## Next steps

<CardGroup cols={2}>
  <Card title="Pagination" icon="list" href="/sdk/typescript/pagination">
    Iterate through paginated results.
  </Card>

  <Card title="Error patterns" icon="book" href="/patterns/errors">
    Common error scenarios across all surfaces.
  </Card>
</CardGroup>
