# API reference

> Exports, instance methods (ask, open, state, ready, reset, …), the global API, no-script triggers, deep links, JSX typings.

Compact lookup for the SDK's exported functions, types, and events.
For explanations + examples, see the [Configuration](/sdk/configuration)
and [Events & hooks](/sdk/events-and-hooks) guides.

---

## Exports

```ts
import {
  // Functions
  init, // Mount (or re-config) a widget
  mount, // Mount a new widget (non-idempotent)
  destroy, // Unmount one or all widgets
  hook, // Register a global lifecycle hook
  getInstance, // Look up a live instance
  getAllInstances, // Get every live instance

  // Constants
  version, // Semver string
  brand, // { id, displayName, cssPrefix, cdnUrl, ... }
  HOOK_WILDCARD, // "*" — wildcard for hook() arguments

  // Types
  type ChatWidgetOptions,
  type ChatWidgetInstance,
  type ResolvedOptions,
  type EventMap,
  type EventName,
  type HookListener,
  type LogEntry,
  type LogLevel,
} from '@helpai/elements';
```

Web component sub-entry (auto-registers the custom element):

```ts
import '@helpai/elements/web-component';
```

React kit (React 18+; everything above plus the component and the hook, so import it all from this one path):

```ts
import {
  HelpAI, // <HelpAI {...options} /> mounts the widget, re-configures it on prop changes, removes it on unmount
  useHelpAI, // useHelpAI(widgetId?) → { state, ask, open, close, reset, track }, re-rendering on state changes
  useHelpAITool, // useHelpAITool(tool) gives the agent a page tool while the component is mounted
  type HelpAIProps,
  type HelpAIControls,
} from '@helpai/elements/react';
```

See [Frameworks → React](/sdk/frameworks#react).

---

## Functions

### `init(opts): ChatWidgetInstance`

Mount a widget (or re-config an existing one).

```ts
function init(opts: ChatWidgetOptions & { target?: HTMLElement }): ChatWidgetInstance;
```

Idempotent — if an instance with the same `widgetId` exists, its
options are merged (via `update`) and the existing handle is
returned.

### `mount(opts): ChatWidgetInstance`

Force a fresh mount. If an instance exists at the same `widgetId`,
it's destroyed first.

```ts
function mount(opts: ChatWidgetOptions & { target?: HTMLElement }): ChatWidgetInstance;
```

Prefer `init()` — safer for SPA route changes.

### `destroy(widgetId?)`

Unmount one or all widgets.

```ts
function destroy(widgetId?: string): void;
```

- With `widgetId` — destroys that one.
- Without — destroys every widget on the page.

### `hook(widgetId, event, listener): () => void`

Register a global lifecycle hook. Fires for any widget matching
`widgetId` + `event`. Wildcards (`"*"`) match anything.

```ts
function hook(widgetId: string, event: string, listener: HookListener): () => void;
```

Returns an unsubscribe function.

### `getInstance(widgetId?): ChatWidgetInstance | null`

Look up a live instance by id.

```ts
function getInstance(widgetId?: string): ChatWidgetInstance | null;
```

`widgetId` defaults to `"default"`. Returns `null` if nothing is
mounted.

### `getAllInstances(): ChatWidgetInstance[]`

Returns every live widget instance on the page.

---

## `ChatWidgetInstance`

```ts
interface ChatWidgetInstance {
  readonly widgetId: string;

  // Panel control
  open(target?: OpenTarget): void; // with a target: that tab, or one Help article / News post in it
  close(): void;
  expand(): void; // toggle normal ↔ expanded
  openForm(id: string): void; // open the chat on that form
  ask(text: string, options?: { send?: boolean }): void; // open the chat with `text` in the composer ({ send: true } sends it)

  // State
  readonly state: WidgetState; // { open, ready, unread, conversationId? }, live
  readonly ready: Promise<void>; // resolves once connected

  // Lifecycle
  reset(): void; // start over as a new anonymous visitor (on logout)
  login(userToken: UserTokenSource): void; // sign the visitor in as your logged-in user (a signed token, or a function returning one)
  track(name: string, data?: Record<string, string | number | boolean>): void; // report your page's own event: analytics + the agent's next message
  registerTool(tool: ModelContextTool, options?: { signal?: AbortSignal }): void; // a tool the agent can use on this page, for every widget on it
  destroy(): void;

  // Events
  on<E extends EventName>(name: E, fn: (payload: EventMap[E]) => void): () => void; // returns unsubscribe fn

  // Re-config
  update(patch: Partial<ChatWidgetOptions>): void; // e.g. update({ tools }) on a route change

  // Host-asserted context (informational, not auth; re-handshakes, same visitor/conversation)
  setUserContext(ctx: Partial<NonNullable<ChatWidgetOptions['userContext']>>): void; // merges into the current user context
  clearUserContext(): void;
  setPageContext(ctx: Partial<NonNullable<ChatWidgetOptions['pageContext']>>): void; // merges into the current page context
  clearPageContext(): void;

  // Runtime diagnostics (module-global across widget instances)
  setDebug(level: LogLevel | boolean | number | string): void;
  readonly debugLevel: () => LogLevel;
  onLog(sink: ((entry: LogEntry) => void) | null): void;
}
```

---

## `ChatWidgetOptions`

The generated, schema-backed field reference is
[`docs/api/configuration.md`](/sdk/options); it is authoritative.
The public input keeps embed identity, context, endpoint overrides, storage,
and callbacks at the top level. Serializable widget settings use focused
nested sections. `mount()` and `init()` require `aiAgentDeploymentId`; the
deployment `publicKey` remains optional for intentional guest mode.

```ts
init({
  publicKey: 'agent_pk_...',
  aiAgentDeploymentId: '01900000-0000-7000-8000-000000000000',
  presentation: {
    mode: 'inline',
    position: 'bottom-right',
    iframe: false,
    size: { width: '420px', height: '640px' },
  },
  behavior: {
    startMinimized: false,
    responseMode: 'streaming',
    disableMessageFeedback: false,
    autoSendSuggestions: false,
    popOutUrl: 'https://chat.example.com',
  },
  theme: 'auto',
  launcher: { variant: 'pill', label: 'Ask AI' },
  header: { actions: ['theme', 'textSize', 'popOut', 'history', 'close'] },
  composer: {
    sendButton: { shape: 'circle', icon: 'arrowUp', variant: 'filled' },
    attachments: { maxCount: 3, accept: 'image/*,application/pdf' },
  },
  feedback: { sound: { enabled: true }, haptics: { enabled: true } },
  i18n: { locale: 'en', availableLocales: ['en', 'fr'] },
  footer: {
    composerDisclaimer: 'AI can make mistakes.',
    poweredBy: { text: 'Powered by Help AI', href: 'https://help.ai' },
  },
});
```

The complete type also exposes `features`, `forms`, `modules`, and `tracking`.
See [Configuration](/sdk/configuration) for behavior and examples.

### `ModelContextTool`

A tool the agent can use on your page, in the WebMCP tool shape ([Page tools](/sdk/page-tools)). On `<web-ai-chat>`
it's the `tools` property or `registerTool()`, since functions can't be attributes.

```ts
interface ModelContextTool<TInput = unknown> {
  name: string; // letters, digits, `_` and `-` (58 at most; others become `_`); the agent sees `site__<name>`
  title?: string;
  description: string; // when to use it (1024 characters at most)
  inputSchema?: Record<string, unknown>; // JSON Schema of an object; none = no input
  annotations?: ToolAnnotations; // `consequentialHint: true` = the visitor approves each call first
  execute(input: TInput, options: { signal: AbortSignal }): unknown; // a JSON result, or throw; `signal` aborts when the visitor moves on
}

interface ToolAnnotations {
  readOnlyHint?: boolean;
  untrustedContentHint?: boolean;
  consequentialHint?: boolean;
  debugging?: boolean;
}
```

### `ActionName`

```ts
type ActionName = 'clear' | 'expand' | 'fullscreen' | 'popOut' | 'sound' | 'history' | 'language' | 'theme' | 'textSize' | 'close';
```

### `FeedbackEvent`

```ts
type FeedbackEvent = 'messageReceived' | 'messageSent' | 'error' | 'voiceStart' | 'voiceStop';
```

### `StringsOverride`

```ts
type LocaleStrings = Partial<Record<StringKey, string>>;

type StringsOverride =
  | LocaleStrings // flat (applies to active locale)
  | Partial<Record<string, LocaleStrings>>; // nested (per-locale)
```

`StringKey` includes: `attach`, `composerPlaceholder`, `send`, `stop`,
`close`, `fullscreen`, `expand`, `history`, `language`, `theme`,
`themeAuto`, `themeLight`, `themeDark`, ... (full list in
`src/i18n/strings.ts`).

### Storage adapter

All adapter methods are synchronous.

```ts
type StorageAdapter = NonNullable<ChatWidgetOptions['storage']>;

interface StorageAdapterShape {
  get(key: string): string | null;
  set(key: string, value: string): void;
  remove(key: string): void;
}
```

### `open(target?)`

```ts
interface OpenTarget {
  to: 'home' | 'chats' | 'help' | 'news';
  id?: string; // a Help article or News post (its content id)
}
```

Opens the panel; with a target it also shows that tab at its root, or the article / post in it. Back from the post
lands on the tab's list. A tab the widget doesn't have (or an unknown target) just opens the panel, with a `warn` log.
A target sent before the widget has connected waits until the deployment's tabs are known. For a site's own buttons:

```ts
document.querySelector('#whats-new')?.addEventListener('click', () => chat.open({ to: 'news' }));
document.querySelector('#help')?.addEventListener('click', () => chat.open({ to: 'help' }));
chat.open({ to: 'news', id: '019a0b6c-7e2a-7000-9d3e-4a5b6c7d8e01' }); // one post
```

### `ask(text, options?)`

```ts
ask(text: string, options?: { send?: boolean }): void;
```

Opens the chat with `text` in the composer, focused, for the visitor to edit and send — the way a suggestion chip
does by default. With `{ send: true }` it goes out right away as the visitor's message in their current conversation
instead. Use it for a site's own "Ask about pricing" buttons:

```ts
document.querySelector('#ask-pricing')?.addEventListener('click', () => chat.ask('How much does the Pro plan cost?'));
document.querySelector('#ask-now')?.addEventListener('click', () => chat.ask('Talk me through the Pro plan', { send: true }));
```

The text waits until the chat can take it, so a button works whenever it's clicked: before the widget has connected
or while a pre-chat form is waiting for an answer; a send also waits for a reply still arriving. A newer `ask`
replaces one that is still waiting. Text is trimmed; an empty string is ignored with a `warn` log. If a send is
refused (for example the visitor is filling an inline form), the text is left in the composer for the visitor to send.

A send speaks for the visitor, so call it from something the visitor did (a click), not on page load.

Without script, mark any element on the page with `data-helpai-ask`; a click asks the default widget. An empty
value asks the element's own text, and `data-helpai-send` on the same element sends it instead of filling the composer:

```html
<button data-helpai-ask="How do I book a session?">Book a session</button> <button data-helpai-ask data-helpai-send>Do you offer gift cards?</button>
```

---

### `state` and `ready`

```ts
interface WidgetState {
  open: boolean; // the panel is showing (always true for inline / page / standalone)
  ready: boolean; // connected: commands act at once
  unread: number; // conversations with replies the visitor hasn't read
  conversationId?: string; // the visitor's current conversation
}
```

`chat.state` is the widget's state right now; the `state` event delivers every change and `unread` fires when the
count changes. `chat.ready` resolves once the widget has connected, so code that needs the widget can `await` it
instead of listening for `handshake`. The unread count is known once the widget has been opened in the page load (a
closed widget makes no request beyond its handshake) and stays live from then on.

```ts
await chat.ready;
const badge = document.querySelector('#help-badge');
function showUnread(count: number) {
  badge.textContent = String(count);
  badge.hidden = count === 0;
}
showUnread(chat.state.unread);
chat.on('unread', showUnread);
```

### `reset()`

Starts over as a new anonymous visitor. Call it when your user logs out on a shared computer: it forgets the stored
visitor, their current conversation (draft and queued messages included), preferences and the user context set with
`setUserContext`, then reconnects as a first visit. Listeners added with `on()` stay, and the handle keeps working.
On `<web-ai-chat>`, also remove a `user-context` attribute you set.

### `login(userToken)`

Signs the visitor in as your logged-in user. `userToken` is a JWT your server signs with the deployment's secret key,
or a function returning a fresh one (called again when the server rejects the current token as expired). The widget
reconnects as that user: their conversations on this browser move to them, and they reach their conversations on any
device where they're signed in. Call `reset()` when they sign out; switching to another user's token starts over the
same way. Also `HelpAI('login', userToken)`, `element.login(userToken)` on `<web-ai-chat>` (not an attribute, so the
token stays out of the DOM), the `userToken` option at `init()`, and `<HelpAI userToken={…} />`. Signing and the
guarantees: [Security → Verified users](/sdk/security-privacy#verified-users-usertoken).

```ts
chat.login(() => fetch('/api/help-ai-token').then((res) => res.text()));
```

---

### `registerTool(tool, options?)`

Gives the agent a tool on this page, for every widget on it: the latest registration of a name wins, and aborting
`options.signal` removes it. Also `HelpAI('registerTool', tool)` (queued before the script loads),
`element.registerTool(tool)` and the React kit's `useHelpAITool(tool)`. Details: [Page tools](/sdk/page-tools).

```ts
chat.registerTool({ name: 'open_booking', description: 'Open the booking form. Use it whenever the visitor wants to book a call.', execute: () => bookingForm.open() });
```

---

### `track(name, data?)`

Reports something the visitor did on your site, outside the chat. It sends a `hostEvent` hit to Elements analytics
(not when tracking is off), and the widget keeps the last 10 for the agent, which reads them with the visitor's next
message. The name is 1–64 characters; the data is flat (strings, numbers, booleans; 256 characters of JSON at most).
Also `HelpAI('track', name, data)`, `element.track(name, data)` and `useHelpAI().track`. Details:
[Analytics → Your own events](/sdk/analytics#your-own-events-chattrack).

```ts
chat.track('plan_viewed', { plan: 'pro' });
```

---

## Events

### `EventMap`

```ts
interface EventMap {
  open: void;
  close: void;
  message: { role: string; text: string };
  error: unknown;
  expand: boolean; // new state
  fullscreen: boolean; // new state
  handshake: HandshakeResponse; // backend's bootstrap response
  localeChange: string; // new BCP-47 tag
  state: WidgetState; // open / ready / unread / conversation changed
  unread: number; // the unread conversation count changed
  // … and more: see `EventMap` in the package types
}

type EventName = keyof EventMap;
```

### `HookListener`

Unified signature for global hooks:

```ts
type HookListener = (instance: ChatWidgetInstance, event: EventName, data: unknown) => void;
```

Within a handler, narrow `data` by event name:

```ts
hook('*', '*', (inst, event, data) => {
  switch (event) {
    case 'message': {
      const m = data as EventMap['message'];
      break;
    }
    case 'expand':
    case 'fullscreen': {
      const flag = data as boolean;
      break;
    }
  }
});
```

---

## Auto-boot via script tag

The auto-boot parser exposes the documented flat `data-*` subset.

```html
<script
  src="https://cdn.help.ai/sdk/js/elements/latest/elements.js"
  data-public-key="agent_pk_..."
  data-ai-agent-deployment-id="01900000-0000-7000-8000-000000000000"
  data-mode="floating"
  data-theme="auto"
  data-position="bottom-right"
  data-launcher-variant="pill"
  data-launcher-label="Ask us"
  data-launcher-icon="spark"
  data-response-mode="streaming"
  data-features-disable-file-upload="true"
  data-features-voice="local"
  data-user-id="user_42"
  data-user-name="Jane Doe"
  data-page-area="support"
  defer
></script>
```

Full attribute list: [Installation § script data attributes](/sdk/installation#attributes).

Leave out `data-ai-agent-deployment-id` to load the bundle without mounting, then call
`HelpAI.init({ … })` from JavaScript ([Installation § Load without mounting](/sdk/installation#load-without-mounting)).

---

## Global `window.HelpAI` API

After the IIFE boots:

```ts
// Direct methods
window.HelpAI.init(opts)
window.HelpAI.mount(opts)
window.HelpAI.destroy(widgetId?)
window.HelpAI.getInstance(widgetId?)
window.HelpAI.getAllInstances()
window.HelpAI.hook(widgetId, event, fn)

// Shortcuts for the default widget
window.HelpAI.open()                  // or .open({ to: 'news', id? })
window.HelpAI.ask(text, { send })    // send defaults to false: fill the composer
await window.HelpAI.ready()          // the default widget, once connected (also when it mounts later)
window.HelpAI.reset()                // a new anonymous visitor (on logout)
window.HelpAI.close()
window.HelpAI.expand()
window.HelpAI.on(event, fn)
window.HelpAI.update(patch)

// Metadata
window.HelpAI.version           // semver string
window.HelpAI.brand             // { id, displayName, cssPrefix, cdnUrl, ... }

// Callable dispatch (for queued calls)
window.HelpAI('init', opts)
window.HelpAI('open')
window.HelpAI('open', { to: 'help' })
window.HelpAI('ask', 'How do I book?') // or ('ask', 'How do I book?', { send: true })
window.HelpAI('ready', (chat) => chat.on('unread', showUnread)) // queue-able before the bundle loads
window.HelpAI('reset')
window.HelpAI('hook', '*', 'message', fn)
```

---

## Pre-boot queues

Used with the async loader snippet — write calls before the bundle
arrives:

```html
<script>
  // Command queue
  window.HelpAI =
    window.HelpAI ||
    function () {
      (window.HelpAI.q = window.HelpAI.q || []).push(arguments);
    };

  // Hook queue — array of [widgetId, event, fn] tuples or { widgetId, event, listener } objects
  window.HelpAIHooks = window.HelpAIHooks || [];
</script>
```

The IIFE drains both on boot.

---

## Web component

Custom element tag name: `<web-ai-chat>`.

The Web Component exposes the same curated parser fields without the
script dataset's outer `data-` prefix:

```html
<web-ai-chat
  public-key="agent_pk_..."
  ai-agent-deployment-id="..."
  mode="inline"
  theme="auto"
  position="bottom-right"
  launcher-variant="pill"
  launcher-label="Ask us"
  launcher-icon="chat"
  actions="theme,history,close"
  height="500px"
></web-ai-chat>
```

Changing a reactive attribute re-resolves the live widget. `iframe`
and `start-minimized` are mount-time-only.

The element has the instance's commands, state and events, so a page drives it directly:

```js
const chat = document.querySelector('web-ai-chat');
chat.open(); // or chat.open({ to: 'help' })
chat.ask('How do I book a session?');
chat.close();
chat.expand();
chat.openForm('lead');
await chat.ready;
chat.on('unread', (count) => console.log(count, chat.state));
chat.reset(); // on logout
```

`on()` subscriptions end when the element leaves the page. Importing `@helpai/elements/web-component` types
`document.querySelector('web-ai-chat')` as `WebAiChatElement` in TypeScript; the
[JSX typings](#jsx-typings) do the same for React, Vue and Svelte markup.

### No-script triggers

Mark any element on the page and a click drives the default widget, with no script. A link keeps working without the
widget; with it, the click opens the widget instead of following the link.

| Attribute                      | Click does                                                                                                            |
| ------------------------------ | --------------------------------------------------------------------------------------------------------------------- |
| `data-helpai-open`             | Opens the panel; a value opens a tab (`home`, `chats`, `help`, `news`) or one article or post (`help/<id>`)           |
| `data-helpai-ask="<text>"`     | Opens the chat with the text in the composer (empty value: the element's own text); add `data-helpai-send` to send it |
| `data-helpai-form="<form id>"` | Opens the chat on a form that has a `manual` trigger                                                                  |
| `data-helpai-close`            | Closes the panel                                                                                                      |

```html
<a href="/help" data-helpai-open="help">Help</a>
<a href="/support/billing" data-helpai-open="help/019a0b6c-7e2a-7000-9d3e-4a5b6c7d8e01">Billing questions</a>
<button data-helpai-ask>Which plan fits a team of 10?</button>
<button data-helpai-form="book-demo">Book a demo</button>
```

### Deep links

A page address can open the default widget on arrival — for support emails, docs and QR codes. Either parameter can
sit in the query or the hash. The widget acts once and drops its parameters from the address (the page's own
parameters and anchors stay), so a reload or a copied link doesn't repeat it. A linked question only fills the
composer; it is never sent for the visitor.

| Address                                | Opens                                                      |
| -------------------------------------- | ---------------------------------------------------------- |
| `?helpai=open`                         | The panel                                                  |
| `?helpai=help/<article id>`            | Help at that article (any target `data-helpai-open` takes) |
| `#helpai-ask=Can+I+export+invoices%3F` | The chat with the question in the composer                 |

### JSX typings

Types-only subpaths register `<web-ai-chat>` with its attributes in each framework's JSX, so attributes autocomplete,
`ai-agent-deployment-id` is required, and a ref or `bind:this` is the typed element:

```ts
import type {} from '@helpai/elements/jsx/react'; // React 19
import type {} from '@helpai/elements/jsx/svelte'; // Svelte 5, e.g. in src/app.d.ts
import type {} from '@helpai/elements/jsx/vue'; // Vue 3 (also set compilerOptions.isCustomElement for the tag)
```

---

## Brand constants

`window.HelpAI.brand` (or `import { brand } from '@helpai/elements'`):

```ts
interface Brand {
  id: string; // e.g. "help-ai"
  name: string; // NPM package name, e.g. "@helpai/elements"
  global: string; // window global, e.g. "HelpAI"
  cssPrefix: string; // "helpai" — used for all CSS classes + custom properties
  displayName: string; // "Help AI" — user-visible brand name
  cdnUrl: string; // "https://cdn.help.ai/sdk/js/elements/latest/elements.js"
  defaultBaseUrl: string; // "https://help.ai" — main origin; modules at /api/agent + /api/data
  defaultTheme: {
    accent: string;
    accentText: string;
    radius: string;
    fontFamily: string;
  };
}
```

Read-only. Useful for:

- "Open in full Help AI dashboard" links.
- "Download the mobile app" banners.
- Dynamic theme fallbacks.

---

## Versioning

Semver. `version` export matches the NPM package.

- **Patch** (1.2.0 → 1.2.1): bug fixes, perf, docs. Auto-upgrade safe.
- **Minor** (1.2.0 → 1.3.0): new options, new events, backward-compatible. Auto-upgrade safe.
- **Major** (1.x.x → 2.0.0): breaking changes. Review the changelog.

Pin a specific version with the CDN URL:

```html
<script src="https://cdn.help.ai/v1.2.0/elements.js" defer></script>
```

Or use `/v1/` for the latest 1.x.x (auto-patches, no major breaks).

---

## See also

- [Configuration](/sdk/configuration) — options with examples + context.
- [Events & hooks](/sdk/events-and-hooks) — event handling deep-dive.
- [Recipes](/sdk/recipes) — copy-paste scenarios.
- [Troubleshooting](/sdk/troubleshooting) — when things go wrong.
