Skip to content

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 ​

bash
npm install @qoniai/qoni

Keep AK/SK on a trusted server:

ts
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:

ts
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 ​

ts
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 ​

ts
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.

ts
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.