Log in

Bots

SDK

If you're using our embed snippet to load your bot into your website, then you already have access to our software development kit. The Software Development Kit (SDK) can be accessed via a property on the global window object window.chatThing.

As a reminder our chat embed script looks like this & you can obtain this by visiting your bots dashboard, clicking on the "Embed" button (top right), then copy the snippet under "Embed as chat widget".

<script
  src="https://chatthing.ai/chat-widget.js"
  type="text/javascript"
  id="98c8bb01-129e-4459-b496-648f6601cd70"
  async
  defer
></script>

If you're using this method to embed your bot, then you have access to the following functionality.

Show or hide the chat window

/* show the chat window */
window.chatThing.show(): void;

/* hide the chat window */
window.chatThing.hide(): void;

/* toggle the chat window */
window.chatThing.toggle(): void;

Show or hide the trigger button

/* show the trigger button */
window.chatThing.showTrigger(): void;

/* hide the trigger button */
window.chatThing.hideTrigger(): void;

/* toggle the trigger button */
window.chatThing.toggleTrigger(): void;

Send messages or start new conversations

/* send a message to the chat interface */
window.chatThing.sendMessage(message: string): void;

/* start a new conversation */
window.chatThing.newConversation(message?: string): void;

Read messages

Read the conversation from the host page — either as a one-off snapshot or by subscribing to updates.

type TPublicChatMessage = {
  id: string;
  role: string; // "user" or "assistant"
  message: string;
  createdAt: string; // ISO 8601
};

/* get a snapshot of the current transcript (user + assistant messages) */
window.chatThing.getMessages(): TPublicChatMessage[];

/* subscribe to messages — the callback runs once per message, including any
   that already exist when you subscribe, then again for each new message.
   returns an unsubscribe function */
window.chatThing.onMessage(
  callback: (message: TPublicChatMessage) => void
): () => void;

Example

// react to every message (existing + new)
const unsubscribe = window.chatThing.onMessage((message) => {
  console.log(`[${message.role}] ${message.message}`);
});

// stop listening later
unsubscribe();

// or read the whole transcript on demand
const transcript = window.chatThing.getMessages();

Messages reflect the conversation shown in the widget. Assistant messages are published once they finish streaming, so onMessage fires with the completed text rather than partial tokens. If you re-initialise the widget with window.chatThing.reload(), re-register your onMessage handler afterwards.

Loading state

Track whether the assistant is currently responding, so you can show a loader elsewhere on your page.

/* whether the assistant is currently responding */
window.chatThing.isLoading(): boolean;

/* subscribe to loading changes — the callback runs immediately with the
   current state, then again whenever it changes. returns an unsubscribe
   function */
window.chatThing.onLoading(callback: (loading: boolean) => void): () => void;

Example

const unsubscribe = window.chatThing.onLoading((loading) => {
  document.querySelector("#my-loader").hidden = !loading;
});

// stop listening later
unsubscribe();

Send message previews (message bubbles)

/* show chat message preview */
window.chatThing.showPreview(message: string, delay?: number): void;

/* hide chat message preview */
window.chatThing.hidePreview(): void;

Extend chat interface theme

type TExtendThemeData = {
  theme?: "dark" | "light";
  colours?: {
    primaryColour?: string;
    primaryColourInverted?: string;
    secondaryColour?: string;
    secondaryColourInverted?: string;
  };
}

/* extend chat interface theme */
window.chatThing.extendTheme(data: TExtendThemeData): void;

Identify user

type TIdentifyUser = {
  name?: string;
};

type TIdentifyUserId = TIdentifyUser & {
  id: string | number;
};

type TIdentifyUserEmail = TIdentifyUser & {
  email: string;
};

/* identify user */
window.chatThing.identifyUser(data: TIdentifyUserId | TIdentifyUserEmail): void;
🚨

The following functions are disabled by default, to make use of these you must first switch them on via your bots web channel settings.

Override or extend the system message

/* overide the system message */
window.chatThing.systemMessage(mode: "override", message: string): void;

/* extend the system message */
window.chatThing.systemMessage(mode: "extend", message: string): void;

Register client side power ups

Client side power-ups allows you to create custom power-ups for your bots allowing it to take actions on behalf of your users. This is extremely powerful and allows you to create AI co-pilots for your apps that can do things like add items to a user's basket etc.

For a detailed example of how to use client-side power-ups to build an AI shopping assistant, checkout this blog post and video: Build an AI shopping co-pilot

/* register a client side power up */
window.chatThing.registerPowerUp(data: TRegisterPowerUpData): TRegisteredPowerUp;

type TPowerUpArgs = Record<string, any>;
type TPowerUpHandler = (args: TPowerUpArgs) => Record<string, any>;
type TPowerUpParameter =
  | {
      type: "string" | "number" | "boolean";
      description: string;
      required: boolean;
      /* optional: restrict the value to a fixed set */
      values?: (string | number | boolean)[];
    }
  | {
      type: "enum";
      description: string;
      required: boolean;
      /* the values the bot may choose from */
      values: (string | number | boolean)[];
    }
  | {
      type: "object";
      description: string;
      required: boolean;
      properties: Record<string, TPowerUpParameter>;
    }
  | {
      type: "array";
      description: string;
      required: boolean;
      items: TPowerUpParameter | TPowerUpParameter[];
    };

type TRegisterPowerUpParameters = Record<string, TPowerUpParameter>;

/* Supply EITHER `parameters` (above) OR `inputSchema` (raw JSON Schema). */
type TRegisterPowerUpParameterSource =
  | { parameters: TRegisterPowerUpParameters; inputSchema?: never }
  | { inputSchema: Record<string, unknown>; parameters?: never };

type TRegisterPowerUpData = TRegisterPowerUpParameterSource & {
  id?: string;
  name: string;
  description: string;
  handler?: TPowerUpHandler;
};

type TRegisteredPowerUp = {
  id: string;
  enabled: boolean;
  setEnabled: (enabled: boolean) => void;
  destroy: () => void;
  handler?: TPowerUpHandler;
};

Example: Adding to cart

Let's demonstrate a basic example where we allow our bot to add products to a users cart. Assume we already have a basic add to cart function in the frontend of our app that looks something like this:

function addToCart(itemId: string, qty: number): Promise<any> {
  // Add to cart implementation goes here. You may post to an API or handle adding to cart
  // entirely on the frontend
}

We can now allow our bot to use the function when a user would like to add a product to the cart by adding a client side power-up.

const addToCartPowerUp = window.chatThing.registerPowerUp({
  name: "Add to cart",
  description: "Add a product to the shopping cart",
  parameters: {
    itemId: {
      type: "string",
      description: "The unique product id",
      required: true,
    },
    qty: {
      type: "number",
      description: "The number of items to add",
      required: true,
    },
  },
  handler: async (args: { itemId: string; qty: number }) => {
    // In the power-up handler you can now call your original add to cart function using
    // the arguments provided by the bot
    try {
      const res = await addToCart(itemId, qty);

      // You should return a string from the handler functions
      return res;
    } catch (e) {
      // If there has been an error return an error message so the bot
      // knows something has gone wrong
      return e.message;
    }
  },
});

Using raw JSON Schema

parameters is a shorthand. It covers the common case well, but it can only express types, descriptions, required-ness, nested objects, arrays and value lists. When you need anything else - pattern, minimum, format, default, integer, oneOf, additionalProperties - supply inputSchema instead and write the JSON Schema directly. It is passed to the model unchanged.

Reach for it whenever you already have a schema: generated from zod, taken from an existing MCP tool, or exposed by a page through WebMCP. Translating it by hand into parameters would only lose the constraints.

window.chatThing.registerPowerUp({
  name: "Book a slot",
  description: "Reserve a slot for the customer",
  inputSchema: {
    type: "object",
    properties: {
      date: { type: "string", pattern: "^\\d{4}-\\d{2}-\\d{2}$" },
      seats: { type: "integer", minimum: 1, maximum: 8, default: 2 },
      contact: { type: "string", format: "email" },
    },
    required: ["date", "seats"],
    additionalProperties: false,
  },
  handler: async (args) => bookSlot(args),
});

Supply one or the other, never both.

The schema is checked before it reaches the model. It must be valid JSON Schema, survive a JSON round trip (so no undefined, functions or NaN), reference only itself ($ref must start with #), and stay within size limits - at most 4000 characters of text, 300 values and 12 levels of nesting. A schema that fails any of these is dropped along with its power-up, and the reason is logged server-side. The limits exist because the schema is sent with every model request for the rest of the conversation.

Restricting a parameter to a fixed set of values

When a parameter only accepts certain values, list them with values. The bot is told which values are valid rather than having to guess, which makes it far less likely to invent one.

window.chatThing.registerPowerUp({
  name: "Filter products",
  description: "Filter the product list",
  parameters: {
    size: {
      type: "string",
      description: "The size to filter by",
      required: true,
      values: ["small", "medium", "large"],
    },
  },
  handler: async (args: { size: string }) => filterProducts(args.size),
});

values works on string, number and boolean parameters. Declaring type: "enum" with a values list does the same thing.

Connect WebMCP tools

WebMCP lets a website expose tools through document.modelContext. Chat Thing can automatically make compatible WebMCP tools available to your bot as client-side power-ups. This allows the bot to use actions already provided by the page, such as searching a catalogue, adding an item to a basket, or navigating within an app.

🚨

Experimental feature

WebMCP is an experimental browser API and may change. Test your integration against the browsers and runtimes you support before using it in production.

Enable WebMCP

First, turn on Advanced SDK features in your bot's web channel settings. This server-side setting is required before the bot will accept tools registered by a webpage.

Then add webMcp: true to window.chatThingConfig before loading the widget:

<script>
  window.chatThingConfig = {
    webMcp: true,
  };
</script>
<script
  src="https://chatthing.ai/chat-widget.js"
  type="text/javascript"
  id="YOUR_BOT_ID"
  async
  defer
></script>

Your page must register its tools with the browser's document.modelContext. Chat Thing discovers compatible tools when the widget starts and keeps them in sync when the runtime emits a toolchange event.

Browser support and fallback behaviour

Chat Thing feature-detects the WebMCP runtime. If document.modelContext is missing or does not provide the required API, the WebMCP bridge logs a warning and stops. The rest of the chat widget continues to work normally.

The SDK does not install a WebMCP shim or polyfill. If your site needs to support browsers without a native runtime, you can provide your own compatible shim before loading the widget. When a native runtime is present, use it instead of the shim.

Compatible tool schemas

A WebMCP tool's inputSchema is carried to the model unchanged, so there is no list of supported keywords to check against. integer, pattern, minimum, format, default, enum, tuples, oneOf and the rest all work.

A tool is skipped only when its schema cannot be carried safely: it is not valid JSON Schema, it would not survive a JSON round trip (NaN, undefined, functions), it uses a remote $ref, or it exceeds the size limits described under Using raw JSON Schema. A skipped tool logs the reason to the browser console, naming what to change.

🚨

Security consideration

Only enable WebMCP on pages you control. Tool names, descriptions, arguments, and results are supplied by the webpage and may be sent to the model. Each tool must validate its inputs, enforce the current user's permissions, and protect destructive or sensitive actions.

See the WebMCP demo and source code for a complete example that prefers the native runtime and falls back to a local shim.