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

# Handle errors from the BlueBubbles SDK

> Learn how to catch ApiError exceptions, inspect HTTP status codes, and cancel in-flight requests using CancelablePromise in the BlueBubbles SDK.

Every method on the `BlueBubblesClient` returns a `CancelablePromise` that rejects with an `ApiError` when the server responds with a non-2xx status code or when a network-level failure occurs. Understanding how to catch and inspect these errors — and how to cancel requests you no longer need — will make your integration more robust and easier to debug.

## The `ApiError` class

When a request fails, the SDK throws an instance of `ApiError`, which extends the native `Error` class. It carries the full context of both the request that was sent and the response that was received.

| Property     | Type                | Description                                                                                                     |
| ------------ | ------------------- | --------------------------------------------------------------------------------------------------------------- |
| `status`     | `number`            | The HTTP status code returned by the server (e.g. `401`, `404`, `500`).                                         |
| `statusText` | `string`            | The HTTP status text associated with the status code (e.g. `"Unauthorized"`).                                   |
| `url`        | `string`            | The full URL of the request that failed.                                                                        |
| `body`       | `any`               | The parsed response body. For BlueBubbles Server errors this is typically a JSON object.                        |
| `request`    | `ApiRequestOptions` | The original request options — method, URL template, headers, query params, and body — that produced the error. |

`ApiError` also inherits `message` and `name` (`"ApiError"`) from `Error`, so you can log it directly or pass it to error-reporting tools without losing information.

## Catching errors with try/catch

Wrap any SDK call in a standard `try/catch` block. Import `ApiError` from the package so you can narrow the type of the caught value before accessing its properties.

```typescript theme={null}
import { BlueBubblesClient, ApiError } from "bluebubbles-sdk";

const client = new BlueBubblesClient({
  BASE: "http://your-server:1234",
  PASSWORD: "your-password",
});

try {
  const message = await client.messages.get({ guid: "msg-guid-here" });
  console.log("Message text:", message.data?.text);
} catch (err) {
  if (err instanceof ApiError) {
    console.error(`Request failed: ${err.status} ${err.statusText}`);
    console.error("URL:", err.url);
    console.error("Response body:", err.body);
  } else {
    // Re-throw unexpected errors (e.g. TypeError from your own code)
    throw err;
  }
}
```

Always check `err instanceof ApiError` before reading SDK-specific properties. Any other thrown value is a bug in your application code or an unexpected runtime error, and you should let it propagate.

## Checking specific status codes

Different status codes require different recovery strategies. The most common ones you will encounter with the BlueBubbles Server are shown below.

<AccordionGroup>
  <Accordion title="401 Unauthorized — wrong or missing password">
    The server rejected your password. Verify that the `PASSWORD` field in your `OpenAPIConfig` matches the password configured in BlueBubbles Server settings.

    ```typescript theme={null}
    try {
      await client.server.getServerMetadata();
    } catch (err) {
      if (err instanceof ApiError && err.status === 401) {
        console.error("Authentication failed. Check your PASSWORD config.");
      }
    }
    ```
  </Accordion>

  <Accordion title="404 Not Found — resource does not exist">
    The requested resource (message, chat, attachment, handle) was not found. Check that the GUID or identifier you are passing is correct.

    ```typescript theme={null}
    try {
      await client.chats.get({ chatGuid: "chat-guid-here" });
    } catch (err) {
      if (err instanceof ApiError && err.status === 404) {
        console.error("Chat not found. The GUID may be incorrect or stale.");
      }
    }
    ```
  </Accordion>

  <Accordion title="500 Internal Server Error — server-side failure">
    BlueBubbles Server encountered an unexpected error. The `body` property often contains a descriptive message from the server. These errors are not caused by your request and typically require checking the server logs.

    ```typescript theme={null}
    try {
      await client.messages.sendText({ requestBody: { chatGuid: "...", message: "Hello" } });
    } catch (err) {
      if (err instanceof ApiError && err.status >= 500) {
        console.error("Server error:", err.body?.message ?? err.statusText);
      }
    }
    ```
  </Accordion>
</AccordionGroup>

## Complete error-handling pattern

The pattern below handles the full range of outcomes — success, known API errors, and unexpected failures — in one place. Adapt it to your own error-reporting infrastructure.

```typescript theme={null}
import { BlueBubblesClient, ApiError } from "bluebubbles-sdk";

const client = new BlueBubblesClient({
  BASE: "http://your-server:1234",
  PASSWORD: "your-password",
});

async function fetchChat(guid: string) {
  try {
    const chat = await client.chats.get({ chatGuid: guid });
    return chat.data;
  } catch (err) {
    if (!(err instanceof ApiError)) {
      // Not an API error — propagate so the caller can handle it
      throw err;
    }

    switch (err.status) {
      case 401:
        throw new Error("Invalid password. Update your BlueBubblesClient config.");
      case 404:
        return null; // Treat missing chats as a nullable result
      case 429:
        throw new Error("Rate limited. Back off and retry.");
      default:
        // Log the full request context for debugging
        console.error({
          status: err.status,
          statusText: err.statusText,
          url: err.url,
          method: err.request.method,
          body: err.body,
        });
        throw err;
    }
  }
}
```

<Note>
  BlueBubbles Server returns `401 Unauthorized` for every request when the password is missing or wrong, including calls to endpoints that appear to be read-only. If you see a flood of 401 errors across multiple services, the most likely cause is a misconfigured `PASSWORD` in your `OpenAPIConfig` rather than a per-endpoint permission issue.
</Note>

## Canceling requests with `CancelablePromise`

Every SDK method returns a `CancelablePromise<T>` — a `Promise` subclass that adds a `cancel()` method. Calling `cancel()` aborts the underlying fetch and causes the promise to reject with a `CancelError` instead of resolving or throwing `ApiError`.

Use `cancel()` when you need to abandon a request that is no longer relevant — for example, when the user navigates away, a component unmounts, or a newer request supersedes an older one.

<Steps>
  <Step title="Hold a reference to the CancelablePromise">
    Do not immediately `await` the promise if you might need to cancel it. Keep a reference so you can call `cancel()` later.

    ```typescript theme={null}
    import { BlueBubblesClient, CancelablePromise, CancelError } from "bluebubbles-sdk";

    const client = new BlueBubblesClient({ BASE: "http://your-server:1234" });

    // Store the promise without awaiting it yet
    let pendingRequest: CancelablePromise<any> | null = null;
    ```
  </Step>

  <Step title="Assign the request and handle CancelError">
    Catch `CancelError` separately so you can distinguish an intentional cancellation from a real failure.

    ```typescript theme={null}
    async function loadMessages(chatGuid: string) {
      // Cancel any previous in-flight request for this slot
      pendingRequest?.cancel();

      pendingRequest = client.messages.list({ requestBody: { chatGuid } });

      try {
        const result = await pendingRequest;
        return result.data;
      } catch (err) {
        if (err instanceof CancelError) {
          // Request was intentionally cancelled — not an error condition
          console.log("Request cancelled.");
          return null;
        }
        throw err;
      } finally {
        pendingRequest = null;
      }
    }
    ```
  </Step>

  <Step title="Cancel on cleanup">
    Call `cancel()` in any cleanup path — `useEffect` return functions in React, `onDestroy` in Angular, or wherever your component or context tears down.

    ```typescript theme={null}
    // React example
    useEffect(() => {
      const req = client.messages.list({ requestBody: { chatGuid } });
      req.then(setMessages).catch((err) => {
        if (!(err instanceof CancelError)) setError(err);
      });

      return () => {
        req.cancel(); // Cleanup: abort the request when the component unmounts
      };
    }, [chatGuid]);
    ```
  </Step>
</Steps>

<Warning>
  Calling `cancel()` after the promise has already resolved or rejected is a no-op — the SDK guards against double-cancellation internally. However, once cancelled, the `CancelablePromise` cannot be restarted. Create a new request if you need to retry after cancellation.
</Warning>

### Inspecting cancellation state

`CancelablePromise` exposes an `isCancelled` getter so you can check the current state synchronously without awaiting the promise.

```typescript theme={null}
const req = client.attachments.get({ guid: "attachment-guid" });

// ... some time later

if (req.isCancelled) {
  console.log("This request was already cancelled.");
} else {
  req.cancel();
}
```
