Node.js / TypeScript SDK
The current SDK is the server-side package @qoniai/qoni. The old @web-agent/sdk, Client, and client.sessions examples are not part of the current SDK.
Install and initialize
npm install @qoniai/qoniKeep AK/SK on a trusted server:
import { Qoni } from "@qoniai/qoni";
const qoni = new Qoni({
accessKey: process.env.QONI_ACCESS_KEY!,
secretKey: process.env.QONI_SECRET_KEY!,
});Get a user delegation token
WebAgent runs on behalf of an end user. Silent delegation needs a real GenAuth user ID:
const { token } = (await qoni.delegateToken({
user: { id: process.env.QONI_USER_ID! },
products: ["doAnything"],
})).data;products can include doAnything, deepResearch, webSearch, and track. Do not hand-roll the SDK's runtime discovery or token-exchange routes.
DoAnything
const run = await qoni.doAnything.run({
token,
prompt: "Open Hacker News and list the first five story titles and links.",
capture: { screenshots: true },
limits: { maxDurationMinutes: 10 },
});
console.log(run.id);
for await (const event of run.events()) {
if (event.type === "progress") console.log(event.data);
if (event.type === "message") console.log(event.data.text);
}
const result = await run.wait();
console.log(result.status, result.output);run() returns a RunHandle, not the old wire envelope. Use status(), events(), wait(), cancel(), and interactionHandle(). Reuse a browser session with session: run.sessionRef; reconnect with qoni.doAnything.attach(run.id, { token }).
DeepResearch and WebSearch
const productToken = (await qoni.delegateToken({
user: { id: process.env.QONI_USER_ID! },
products: ["deepResearch", "webSearch"],
})).data.token;
const research = await qoni.deepResearch.run({
token: productToken,
prompt: "Research the main browser-automation trends in 2026.",
depth: "standard",
});
const report = await research.wait();
const search = await qoni.webSearch.run({
token: productToken,
prompt: ["browser automation 2026"],
maxResultsPerQuery: 5,
});
const hits = await search.wait();DoAnything, DeepResearch, and WebSearch expose id, events(), wait(), and cancel(). Track compatibility is described below.
WebSearch creation is always asynchronous on the server. wait() only waits for the terminal run; there is no runAsync(), refine(), or follow-up method.
Track
Published npm 0.9.0 uses legacy Track routes incompatible with the current backend. The unpublished repair candidate uses /track/tracks, requires an explicit interval or daily schedule, and has no Track SSE or question/intervention capability. See Track for candidate examples, recent 50 checks, and task references; these are not released 0.9.0 features.
Events and human interaction
The SDK normalizes wire events to progress, message, interaction, screenshot, and done (plus product-specific events). Use interactionHandle(event.data) for an interaction instead of hard-coding a nonexistent client.messages.intervene() method.
for await (const event of run.events()) {
if (event.type !== "interaction") continue;
const interaction = run.interactionHandle(event.data);
if (interaction.can("confirm")) await interaction.confirm();
}Each namespace also exposes .api as a wire-level escape hatch, for example qoni.deepResearch.api.followUp() and .feedback(). Those methods follow the OpenAPI snake_case contract.
Obsolete calls
These are not current high-level APIs: new Client({ apiKey, projectId }), client.sessions, client.events, runAsync, run_async, webSearch.refine, track.listSnapshots, track.getSnapshot, and track.retryDelivery.