Skip to main content
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.
PropertyTypeDescription
statusnumberThe HTTP status code returned by the server (e.g. 401, 404, 500).
statusTextstringThe HTTP status text associated with the status code (e.g. "Unauthorized").
urlstringThe full URL of the request that failed.
bodyanyThe parsed response body. For BlueBubbles Server errors this is typically a JSON object.
requestApiRequestOptionsThe 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.
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.
The server rejected your password. Verify that the PASSWORD field in your OpenAPIConfig matches the password configured in BlueBubbles Server settings.
try {
  await client.server.getServerMetadata();
} catch (err) {
  if (err instanceof ApiError && err.status === 401) {
    console.error("Authentication failed. Check your PASSWORD config.");
  }
}
The requested resource (message, chat, attachment, handle) was not found. Check that the GUID or identifier you are passing is correct.
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.");
  }
}
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.
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);
  }
}

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.
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;
    }
  }
}
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.

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.
1

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.
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;
2

Assign the request and handle CancelError

Catch CancelError separately so you can distinguish an intentional cancellation from a real failure.
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;
  }
}
3

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.
// 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]);
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.

Inspecting cancellation state

CancelablePromise exposes an isCancelled getter so you can check the current state synchronously without awaiting the promise.
const req = client.attachments.get({ guid: "attachment-guid" });

// ... some time later

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