ask
Send one request and receive normalized text, speech, and display items.
Use the Node SDK when your client or agent runs in JavaScript or TypeScript.
The package name is @thalovant/sdk. Node.js 20 or newer is required.
Install with npm install @thalovant/sdk.
npm install @thalovant/sdkimport { ThalovantClient, ThalovantControlPlane } from "@thalovant/sdk";
const api = new ThalovantControlPlane();
const publicHubs = await api.listPublicHubs({ limit: 12 });for (const hub of publicHubs.data as Array<{ id: string; slug: string; title: string }>) { console.log(hub.id, hub.slug, hub.title);}
const result = await api.createClientIdentity("hub-id", { name: "node-demo-client", preferredProtocols: ["wss", "https", "mqtt"],});
const client = new ThalovantClient(result.identity, { protocol: "wss" });try { const reply = await client.ask("Tell me a short clean joke."); console.log(reply.text);} finally { await client.close();}new ThalovantControlPlane() uses https://api.thalovant.com by default.
Accounts with multi-factor authentication enabled must include a TOTP code or a one-time recovery code with the login. Without one, the API rejects the sign-in with HTTP 401 and code mfa_required. Both options are sent only when provided. MFA support needs SDK 0.2.23 or newer.
// Or use a one-time recovery code instead:Device login asks your browser to approve the sign-in, so scripts, bots, and agents never handle your password. The SDK prints a short code and the address https://dash.thalovant.com/activate, opens that page when it can, and waits while you approve the request in the dashboard with your normal sign-in, including Google sign-in or MFA. On approval the SDK holds a scoped, revocable API token.
Device login needs SDK 0.2.25 or newer. Approving the request needs a paid workspace plan; a free plan gets HTTP 402.
// Prints: To sign in, visit https://dash.thalovant.com/activate and enter the code XXXX-XXXXconst token = await api.loginWithBrowser({ clientName: "my-laptop" });
// api.accessToken is now set, exactly like after api.login(...).const page = await api.listHubs({ limit: 50 });Manage the resulting token on the dashboard’s API Tokens page.
Pass a stored API token when the process should start authenticated, such as CI jobs and services. Mint one on the API Tokens page or with loginWithBrowser; the page shows scopes, expiry, and last use, and can revoke the token at any time.
const api = new ThalovantControlPlane("https://api.thalovant.com", { accessToken: process.env.THALOVANT_API_TOKEN,});
// Ready immediately; no login call needed.const page = await api.listHubs({ limit: 50 });The SDK also runs in browsers behind a bundler. This needs SDK 0.2.24 or newer.
The control plane, including login, listPublicHubs, createClientIdentity, memory, and analytics, uses the global fetch. ThalovantClient works over the wss and https protocols using the global WebSocket and Web Crypto for payload encryption.
The package ships a browser map alongside its exports entry, so bundlers such as esbuild, webpack, Vite, and Rollup pick browser-safe modules automatically and never pull ws, mqtt, or node: builtins into web bundles. Bundle it like any other dependency:
esbuild app.js --bundle --platform=browser --outfile=dist/app.js// app.js — runs in the browser after bundlingimport { ThalovantClient, ThalovantControlPlane } from "@thalovant/sdk";
const api = new ThalovantControlPlane();await api.login(email, password);const result = await api.createClientIdentity(hubId, { name: "web-kiosk" });
const client = new ThalovantClient(result.identity, { protocol: "wss" });const reply = await client.ask("Hello from the browser.");console.log(reply.text);await client.close();Browser caveats:
mqtt protocol stays Node-only. Constructing the MQTT transport in a browser throws ThalovantUnsupportedProtocolError with a clear message; use wss or https instead.ThalovantIdentity.fromFile(), fromConfig(), and defaultConfigPath() throw in browsers; construct ThalovantIdentity from an in-memory object, such as the result of createClientIdentity.*Async variants, which the transports already use on both platforms.import { ThalovantClient } from "@thalovant/sdk";
const client = await ThalovantClient.fromConfig({ profile: "prod", protocol: "wss" });try { const reply = await client.ask("What can this hub do?"); console.log(reply.text);} finally { await client.close();}Raw identity files work too:
import { ThalovantClient } from "@thalovant/sdk";
const client = await ThalovantClient.fromIdentityFile("_identity.json");try { const reply = await client.ask("What can this hub do?"); console.log(reply.text);} finally { await client.close();}Environment variables work too:
import { ThalovantClient } from "@thalovant/sdk";
const client = ThalovantClient.fromEnv();const identity = result.identity;
console.log(identity.enabledProtocols());console.log(identity.endpointFor("wss"));console.log(identity.endpointFor("https"));console.log(identity.endpointFor("mqtt"));
for (const protocol of ["wss", "https", "mqtt"] as const) { if (!identity.supportsProtocol(protocol)) continue; if (protocol === "mqtt" && !identity.mqtt) continue;
const client = new ThalovantClient(identity, { protocol }); try { const reply = await client.ask(`Reply over ${protocol}.`); console.log(protocol, reply.text); } finally { await client.close(); }}MQTT requires the identity.mqtt broker credentials returned for that client.
For broker details, see MQTT.
import { buildClientContext } from "@thalovant/sdk";
const context = buildClientContext({}, { userId: "user-42", userName: "Ada", source: "checkout-kiosk", platform: "kiosk", locale: "en-US", channel: "chat",});
const reply = await client.ask("Show the next instruction.", { context });console.log(reply.text);import { EVENT_SPEAK, EVENT_UTTERANCE_HANDLED } from "@thalovant/sdk";
const sub = client.on(EVENT_SPEAK, event => { console.log(event.text);});
try { await client.sendUtterance("Say the current status."); await client.waitForEvent(EVENT_UTTERANCE_HANDLED, { timeoutMs: 12_000 });} finally { sub.close();}ask
Send one request and receive normalized text, speech, and display items.
waitForEvent
Wait for one matching hub event with a timeout.
sendAction
Send a button, menu, or tool action.
sendCode
Send a barcode, QR value, serial number, or typed exact value.
For the full method list, see SDK Functions.
Use api.getOperation(operationId) to follow an accepted command; see Operations.
Since SDK 0.2.28 the control plane can create hubs, runtime groups, and skill installs. Browsing the catalog with listMarketplaceSkills() needs hubs:read and works on any plan. createHub, createRuntimeGroup, installRuntimeGroupSkill, releaseHub, and releaseRuntimeGroup need hubs:write and a paid plan.
const group = await api.createRuntimeGroup({ name: "kiosks" });let hub = await api.createHub({ name: "joke-garden", runtimeGroupId: group.id as string, spec: {} });
hub = await api.getHub(hub.id as string);await api.updateHub(hub.id as string, { active: false }, { etag: hub.etag as string });updateHub and deleteHub require the hub’s current etag. See Provision Hubs for the full flow, the immutable fields, and the error table.
| Symptom | Check |
|---|---|
| Missing access token | Call api.login(...) or api.loginWithBrowser(...) before private API actions, or pass accessToken to ThalovantControlPlane. |
HTTP 401 with code mfa_required |
Pass otpCode or recoveryCode in the login options. |
| API access requires a paid plan | Upgrade the workspace before provisioning private resources through the API. |
| Unsupported protocol | Enable that protocol on the hub and create a fresh identity. |
| MQTT fails immediately | Confirm the identity has mqtt broker credentials, and that the client is not running in a browser bundle. |