Widget API Reference
The Appilot Widget exposes a JavaScript API for programmatic control.
The widget boots via the script tag with data-* attributes (Installation) or programmatically with bootAppilotWidget() from @betterknow/appilot. The loaded widget exposes window.Appilot for panel and theme control. Features marked as Planned below require a future release.
Initialization
bootAppilotWidget(options)
Load the widget bundle and keep it authenticated for the life of the page. Idempotent while the widget is healthy: calls made while it is booting or ready are ignored. After a failed boot the next call retries from scratch.
import { bootAppilotWidget } from '@betterknow/appilot';
bootAppilotWidget({
widgetScriptUrl: 'https://cdn.appilot.com/widget/v1/appilot.esm.js',
widgetKey: 'wk_live_a1b2c3d4...',
tokenEndpoint: '/api/widget/token',
theme: 'dark',
});
Options (AppilotWidgetBootOptions):
| Field | Type | Required | Description |
|---|---|---|---|
widgetScriptUrl | string | Yes | Widget bundle URL. When absent, the assistant is disabled. |
widgetKey | string | No | Publishable widget key. Optional on a registered domain; required for localhost/dev and server-to-server. |
appilotApiUrl | string | No | Appilot backend base URL. |
tokenEndpoint | string | No | Identity relay on your own backend that mints an identified widget token (default /api/widget/token). |
tokenBody | Record<string, unknown> | No | JSON body sent to the relay (omitted = relay default). |
getBearer | () => string | null | undefined | No | Supplies the logged-in user's session bearer for the relay call, so the relay federates that user. Return null when there is no session. |
position | 'bottom-right' | 'bottom-left' | No | Button position. |
theme | string | No | Color theme (light, dark, auto, host). See Theming & dark mode. |
language | string | No | UI language (en, es, de). |
brandName | string | No | Header brand text and the name signing assistant turns (data-brand-name). Defaults to your organization name. See Brand name. |
surfaceLabel | string | No | How the app names its assistant in console diagnostics. |
timeoutMs | number | No | Maximum time for token relay + bundle readiness (default 10000). |
Returns: Promise<void>
getWidgetAvailability() / subscribeWidgetAvailability(listener)
Observe boot state so your page can render an honest "assistant unavailable" fallback. getWidgetAvailability() returns the current state ('unknown' | 'booting' | 'ready' | 'unavailable'); subscribeWidgetAvailability(listener) calls listener on every change and returns an unsubscribe function. They pair directly with React's useSyncExternalStore(subscribeWidgetAvailability, getWidgetAvailability).
import { getWidgetAvailability, subscribeWidgetAvailability } from '@betterknow/appilot';
const stop = subscribeWidgetAvailability((state) => {
console.log('assistant is now', state);
});
// call stop() to unsubscribe
destroyAppilot()
destroyAppilot is planned for a future release. There is no teardown call today; a booted widget lives for the life of the page.
Remove the widget from the page and clean up all resources (WebSocket, event listeners).
import { destroyAppilot } from '@betterknow/appilot';
destroyAppilot();
updateAppilot(config)
updateAppilot is planned for a future release.
Update widget configuration at runtime. Only the provided fields are changed.
import { updateAppilot } from '@betterknow/appilot';
updateAppilot({ theme: 'dark', language: 'de' });
Panel Control
Appilot.open(options?)
Open the assistant panel. With prefill, the text lands in the composer for
the user to review and send; nothing is ever auto-submitted, so your page
can suggest a prompt but cannot speak on the user's behalf.
// Just open the panel:
Appilot.open();
// Open with a suggested prompt in the composer:
document.querySelector('#generate-scenarios').addEventListener('click', () => {
window.Appilot.open({
prefill: 'Generate three scenarios for this Field',
});
});
Use it to make "Ask the assistant" affordances first-class: a button next to your feature opens the panel with the right prompt ready, instead of telling the user what to type.
Appilot.close()
Close the assistant panel (same animation as the launcher).
Appilot.close();
Appilot.toggle() / Appilot.isOpen()
toggle and isOpen are planned for a future release.
Theme control
Appilot.setTheme(mode)
Set the theme policy at runtime: 'light' | 'dark' | 'auto' | 'host'. Takes
effect immediately without re-mounting the widget, so the open conversation is
preserved. Use it to keep the assistant in lockstep with your own theme toggle.
// Wire it to your app's light/dark toggle:
myThemeToggle.addEventListener('change', (e) => {
window.Appilot.setTheme(e.target.checked ? 'dark' : 'light');
});
See Theming & dark mode for the full model,
including the no-code data-theme="host" option.
Messaging
Programmatic messaging (sendMessage, setContext) is planned for a future release.
Appilot.sendMessage(text)
Programmatically send a message to the assistant. Opens the panel if closed.
Appilot.sendMessage('How do I fill out the tax ID field?');
Appilot.setContext(context)
Provide additional context for the assistant. This is merged with the auto-detected page context.
Appilot.setContext({
page: 'checkout',
step: 3,
user: { plan: 'enterprise' },
});
Events
Event subscriptions (Appilot.on) are planned for a future release.
Appilot.on(event, callback)
Subscribe to widget events. Returns an unsubscribe function.
const unsubscribe = Appilot.on('message', (data) => {
console.log('Assistant replied:', data.text);
});
// Later: stop listening
unsubscribe();
Available events
| Event | Payload | Description |
|---|---|---|
ready | {} | Widget initialized and ready |
open | {} | Panel opened |
close | {} | Panel closed |
message | { text, role, timestamp } | Message sent or received |
error | { code, message } | Error occurred |
DOM events
Independently of Appilot.on, the widget dispatches DOM CustomEvents the
host page can listen to today:
| Event | Target | detail | When |
|---|---|---|---|
appilot:turn-complete | document | { conversationId: number | null, tools: string[] } | After each assistant turn completes. tools is the deduped list of tool names invoked during the turn (empty when none). Never carries message content. |
Use it to refresh your app's data when the assistant writes through your HTTP tools:
document.addEventListener('appilot:turn-complete', (e) => {
const { tools } = e.detail;
if (tools.includes('create_record')) {
refreshRecordList();
}
});
Global Object
When loaded via script tag (IIFE), the widget exposes window.Appilot:
<script src="https://cdn.appilot.com/widget/v1/appilot.js" data-api-key="wk_live_..." async></script>
<script>
window.addEventListener('appilot:ready', function () {
Appilot.open();
Appilot.sendMessage('Help me with this form');
Appilot.on('message', function (data) {
console.log(data.text);
});
});
</script>
Federated (delegated) Authentication
To authenticate users without showing a login UI, your backend mints a short-lived Appilot token via the federation handshake and passes it to the widget. This is the common case; the full walkthrough is in Connecting your users.
Backend call
The handshake requires your server secret (wsk_secret_...), which is private and lives only on your backend. The public key alone cannot mint a user token.
curl -X POST https://api.appilot.com/widget/token \
-H "X-Widget-Key: wk_live_a1b2c3d4..." \
-H "X-Widget-Secret: wsk_secret_..." \
-H "Content-Type: application/json" \
-d '{ "externalId": "your-app-user-id", "email": "user@example.com", "displayName": "Jane" }'
Response:
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"expiresIn": 3600
}
Frontend usage
Point bootAppilotWidget at your backend relay. It performs the handshake for you (POST to tokenEndpoint, attaching the host bearer from getBearer), loads the widget with the returned token, and re-relays before the token expires:
import { bootAppilotWidget } from '@betterknow/appilot';
bootAppilotWidget({
widgetScriptUrl: 'https://cdn.appilot.com/widget/v1/appilot.esm.js',
// widgetKey optional on a registered domain
tokenEndpoint: '/api/appilot-token', // your backend mints the identified token here
getBearer: () => getMyAppSessionToken(), // so the relay federates the logged-in user
});
POST /widget/token uses your server secret and MUST run on your backend, never in the browser. The public widget key alone cannot mint a user token.
TypeScript Types
The @betterknow/appilot package includes full TypeScript declarations:
import type { AppilotWidgetBootOptions } from '@betterknow/appilot';
const options: AppilotWidgetBootOptions = {
widgetScriptUrl: 'https://cdn.appilot.com/widget/v1/appilot.esm.js',
widgetKey: 'wk_live_...',
theme: 'dark',
};