Talkory Node.js SDK
Ask once, hear from up to 5 LLMs (GPT, Claude, Gemini, Grok, Perplexity), and get back an AI consensus - fully typed, zero runtime dependencies. Install with npm install talkory.
npm install talkory
// quickstart.ts
import { Talkory } from "talkory";
const client = new Talkory(); // reads TALKORY_API_KEY
const result = await client.queries.run(
"Best commuter motorcycles under $5000?"
);
console.log(result.consensus);
console.log(result.confidence);Installation
The SDK targets Node.js 18+ (or any runtime with a global fetch, such as Bun or Deno), has zero runtime dependencies (built on the platform's native fetch), and ships as a dual ESM/CJS package with bundled TypeScript declarations.
npm install talkory| Detail | Value |
|---|---|
| Node.js | 18+ (also works on Bun, Deno, or any runtime with global fetch) |
| Dependencies | None at runtime |
| Module formats | ESM (import) and CommonJS (require), both typed |
| License | MIT |
Works with either module system out of the box:
// ESM / TypeScript
import { Talkory } from "talkory";
// CommonJS
const { Talkory } = require("talkory");Quickstart
Every method is async and returns a typed Promise: result.consensus is the synthesized answer, result.commonAnswer is the content every model agreed on, and result.confidence is a 0-100 score.
import { Talkory } from "talkory";
const client = new Talkory(); // reads TALKORY_API_KEY from the environment
const result = await client.queries.run("Best commuter motorcycles under $5000?");
console.log(result.consensus); // synthesized consensus answer
console.log(result.commonAnswer); // what the models agreed on
console.log(result.confidence); // 0-100
console.log(result.charged); // USD charged for this runqueries.run() waits for every model plus the consensus before resolving (can take a few minutes); its default timeout is 600 seconds.
Authentication
Create a key at app.talkory.ai โ Settings โ API Keys, then either set it as an environment variable or pass it explicitly:
export TALKORY_API_KEY="tk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"import { Talkory } from "talkory";
// reads TALKORY_API_KEY automatically
const client = new Talkory();
// or pass a key explicitly
const client2 = new Talkory({ apiKey: "tk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" });toString(), console.log, and error messages all show at most tk_live_***a1b2. Every request is sent over HTTPS (a plain http:// baseUrl is refused unless you explicitly pass allowInsecure: true, for local testing only) with a User-Agent: talkory-js/{version} {runtime} header.Streaming
client.queries.stream() returns an AsyncGenerator you consume with for await...of, yielding typed events as each model responds live, ending with a done event carrying the consensus.
for await (const event of client.queries.stream("Compare Python web frameworks in 2026")) {
if (event.type === "model_chunk") {
process.stdout.write(event.chunk);
} else if (event.type === "done") {
console.log("\n\nConsensus:", event.consensus);
}
}Events are a discriminated union on type: query_created, model_stream_start, model_chunk, model_result, model_error, round1_done, round2_start, round2_done, consensus_start, and done - TypeScript narrows the event's fields automatically once you check event.type. Any future event type your SDK version doesn't know about arrives as an UnknownEvent instead of throwing, so streams keep working across versions.
StreamInterruptedError carrying a queryId. The run keeps going on our side and is still charged, so recover the result with client.queries.retrieve(err.queryId) instead of re-running the prompt (which would charge you twice).Choosing models & options
run(), create() and stream() all accept the same options object.
| Option | Type | Default | Description |
|---|---|---|---|
models | string[] | all plan models | Subset of model ids from models.list() (max 10). Omit to run every model on your plan. |
country | string | "global" | ISO country code (e.g. "in") to localise answers. |
recursive | boolean | false | Round-2 self-review: each model critiques and improves its own answer before consensus. |
consensus | boolean | true | false skips consensus generation (cheaper); generate it later with queries.consensus(). |
const models = await client.models.list(); // free
const result = await client.queries.run("Is a heat pump worth it in Delhi?", {
models: ["gpt", "claude"], // subset (max 10)
recursive: true, // round-2 self-review before consensus
country: "in", // localization
});Deferred consensus, generated later (idempotent - cached re-calls charge $0):
const result = await client.queries.run("...", { consensus: false });
const later = await client.queries.consensus(result.id);Key features
- Async-first, no callbacks. Every call is a
PromiseorAsyncGenerator-awaita result, orfor await...ofa stream. - Auto-paginating history.
client.queries.list()is an async generator that walks every page for you vianextCursor. - Fire-and-forget.
create()returns instantly with an id; callwait()later, with gentle backoff polling built in. - Typed errors. Every documented API error maps to a specific error class carrying
code,status, andrequestId. - Wallet safety. Paid POST requests are never auto-retried, so a network blip can't double-charge your wallet. Free idempotent GETs retry automatically on 429/500/503, honoring
Retry-After. - Dual ESM/CJS. Works with both
importandrequire, with bundled, correct TypeScript declarations either way.
Client reference
ASYNCclient.queries.run(prompt, options?)Paid ยท charged from wallet
Sends the prompt, waits for every model plus the consensus, and resolves with one typed QueryResult. The simplest way to use the SDK. Default timeout is 600 seconds (overridable via options.timeoutMs).
const result = await client.queries.run("Best commuter motorcycles under $5000?", {
models: ["gpt", "claude", "gemini"],
recursive: false,
});
console.log(result.consensus, result.commonAnswer, result.confidence, result.charged);ASYNCclient.queries.stream(prompt, options?)Paid ยท charged from wallet
Returns an AsyncGenerator<StreamEvent> of typed events as each model responds live. A fatal error SSE event is thrown as the mapped TalkoryError subclass rather than yielded, so you only need to handle model_chunk/model_result/done in the happy path.
for await (const event of client.queries.stream("Compare Python web frameworks in 2026")) {
if (event.type === "model_chunk") {
process.stdout.write(event.chunk);
} else if (event.type === "model_result") {
console.log(`\n${event.modelId} scored ${event.score}`);
} else if (event.type === "done") {
console.log("\n\nConsensus:", event.consensus);
}
}ASYNCclient.queries.create() / .wait(id)Paid ยท charged from wallet
Fire-and-forget pattern: create() starts a run and resolves immediately with an id (202 Accepted), so a dropped connection never loses the run. wait(id) polls retrieve(id) with gentle backoff (starting at 2s, growing ร1.5 up to 10s) until the run leaves "running", up to a 15 minute default timeout.
const run = await client.queries.create("Best commuter bikes under $5000?");
console.log(run.id); // save this - the run continues even if you disconnect
const result = await client.queries.wait(run.id);
console.log(result.consensus);ASYNCclient.queries.consensus(id)Paid ยท idempotent
Generates the consensus and common answer for a query that was run with consensus: false. If the consensus already exists it is returned as-is with charged: 0, so calling it twice never double-charges.
const result = await client.queries.run("Best NAS for home use?", { consensus: false });
const consensus = await client.queries.consensus(result.id);
console.log(consensus.consensus, consensus.confidence);ASYNCclient.queries.retrieve(id) / .list()Free ยท no wallet charge
retrieve(id) fetches a single query by id (used for polling or recovering after a dropped stream). list() is an AsyncGenerator that auto-paginates through your past queries, newest first, walking every page via nextCursor for you.
const query = await client.queries.retrieve("7f9e4c2a-1b8f-4a21-9d3c-e77aa53b6c10");
console.log(query.status);
for await (const q of client.queries.list({ limit: 50 })) {
console.log(q.id, q.prompt, q.charged);
}ASYNChealth.check() / models.list() / wallet.get() / usage.get()Free ยท no wallet charge
Utility calls that never touch your wallet: check live provider health, list available models, read your wallet balance, or pull aggregated usage for your key.
console.log((await client.health.check()).status);
console.log(await client.models.list());
console.log((await client.wallet.get()).balance);
console.log((await client.usage.get({ from: "2026-06-01" })).requests.total);Error handling
Every documented API error maps to a typed error class extending TalkoryError, carrying code, status, message, and requestId, so you can branch with instanceof or log the request id for support.
import { InsufficientBalanceError, RateLimitError, TalkoryError } from "talkory";
try {
const result = await client.queries.run("Best commuter motorcycles under $5000?");
} catch (err) {
if (err instanceof RateLimitError) {
await sleep((err.retryAfter ?? 30) * 1000); // seconds
} else if (err instanceof InsufficientBalanceError) {
// top up at app.talkory.ai
} else if (err instanceof TalkoryError) {
console.error(err.code, err.status, err.requestId);
} else {
throw err;
}
}| Error class | When it happens |
|---|---|
AuthenticationError | Missing or invalid API key. |
InsufficientBalanceError | Wallet has no balance for a paid call. |
InvalidModelError | Unknown model id, or model not on your plan. |
QueryInProgressError | Too many concurrent queries for this key; wait for one to finish. |
RateLimitError | Too many requests; carries retryAfter in seconds. |
APIConnectionError / APITimeoutError | Network failure or client-side timeout reaching api.talkory.ai. |
StreamInterruptedError | An SSE stream dropped after the run started; carries queryId to recover the result. |
TalkoryError | Base class for every other API error. |
Security
| Property | Behavior |
|---|---|
| Key resolution | Explicit apiKey constructor option, or TALKORY_API_KEY environment variable. |
| Key redaction | Keys never appear in toString(), console.log, or error messages - shown as tk_live_***a1b2 at most. |
| Transport | HTTPS enforced; plain http:// refused unless allowInsecure: true is explicitly set (local testing only). |
| Telemetry | Zero telemetry. The only outbound traffic is the API calls you make. |
| User-Agent | talkory-js/{version} {runtime} |
FAQ
How do I install the Talkory Node.js SDK?
Run npm install talkory. It requires Node.js 18+ (or any runtime with a global fetch, such as Bun or Deno), has zero runtime dependencies, and ships as dual ESM/CJS with bundled TypeScript declarations.
How does the Talkory Node.js SDK authenticate?
Set the TALKORY_API_KEY environment variable and call new Talkory() with no arguments, or pass an explicit key with new Talkory({ apiKey: "tk_live_xxxxxxxx" }). Keys are redacted from toString(), console.log, and error messages.
Does the Talkory Node.js SDK support streaming?
client.queries.stream(prompt) returns an async generator you consume with for await...of, yielding typed events including model_chunk for live text and a final done event with the consensus.
Does the Talkory Node.js SDK work with both import and require?
Yes. The package ships a dual ESM/CJS build, so both import { Talkory } from "talkory" and const { Talkory } = require("talkory") work out of the box, with correct TypeScript types either way.
Need a key? Head to your API settings. Prefer REST or Python? See the API documentation or Python SDK docs. Package details and release history live on npm โ, and the source is on GitHub โ.