Skip to content

Full examples

Complete, copy-pasteable programs for the common tasks. Each one is a full file — set FINN_URL and FINN_API_KEY in your environment and run it.

Run with node file.mjs. Requires Node ≥ 18.

Open a connection, stream the answer token by token, then print the final result.

stream.mjs
import { FinnClient, FinnError } from '@riseanalytics/finn-sdk';
const finn = new FinnClient({
url: process.env.FINN_URL,
apiKey: process.env.FINN_API_KEY,
timeoutMs: 15000,
});
await finn.connect();
try {
const turn = finn.sendMessage('How many active members do I have?');
for await (const chunk of turn) {
process.stdout.write(chunk.text);
}
const result = await turn.completed;
console.log('\n---');
console.log('conversationId:', result.conversationId);
console.log('tokenUsage:', result.tokenUsage);
} catch (err) {
if (err instanceof FinnError) console.error(`\nFinn failed (${err.code}): ${err.message}`);
else throw err;
} finally {
await finn.disconnect();
}

When you only want the final text, skip the loop.

ask.mjs
import { FinnClient } from '@riseanalytics/finn-sdk';
const finn = new FinnClient({ url: process.env.FINN_URL, apiKey: process.env.FINN_API_KEY });
await finn.connect();
const { text, tokenUsage } = await finn.ask('Summarize member growth this quarter.');
console.log(text);
console.log('tokens:', tokenUsage);
await finn.disconnect();

Ask for JSON and get a parsed object back. Tell Finn “JSON only” in the prompt.

json.mjs
import { FinnClient, FinnError } from '@riseanalytics/finn-sdk';
const finn = new FinnClient({ url: process.env.FINN_URL, apiKey: process.env.FINN_API_KEY });
await finn.connect();
try {
const rows = await finn.askJson(`
How many active members did I have over the last 3 months?
Return ONLY a JSON array like [{ "month": "2026-06", "active_members": 0 }].
No prose, no markdown.
`);
for (const row of rows) console.log(row.month, '->', row.active_members);
} catch (err) {
if (err instanceof FinnError && err.code === 'invalid_json') {
console.error('Not JSON. Raw answer:\n', err.details.raw);
} else throw err;
} finally {
await finn.disconnect();
}

Thread a conversation id across turns to keep context.

conversation.mjs
import { FinnClient } from '@riseanalytics/finn-sdk';
const finn = new FinnClient({ url: process.env.FINN_URL, apiKey: process.env.FINN_API_KEY });
await finn.connect();
const first = await finn.ask('How many active members do I have?');
console.log('Q1:', first.text);
const second = await finn.ask('How many of them joined in the last 90 days?', {
conversationId: first.conversationId,
});
console.log('Q2:', second.text);
await finn.disconnect();

Stop generation partway through; the turn settles with interrupted set.

interrupt.mjs
import { FinnClient } from '@riseanalytics/finn-sdk';
const finn = new FinnClient({ url: process.env.FINN_URL, apiKey: process.env.FINN_API_KEY });
await finn.connect();
const turn = finn.sendMessage('Summarize every transaction this year in detail…');
setTimeout(() => turn.interrupt(), 2000);
for await (const chunk of turn) process.stdout.write(chunk.text);
const result = await turn.completed;
console.log('\ninterrupted?', result.interrupted);
await finn.disconnect();

Catch a FinnError and branch on its code.

robust.mjs
import { FinnClient, FinnError } from '@riseanalytics/finn-sdk';
async function askWithRetry(finn, question, tries = 3) {
for (let attempt = 1; attempt <= tries; attempt++) {
try {
return await finn.ask(question);
} catch (err) {
if (!(err instanceof FinnError)) throw err;
if ((err.code === 'rate_limited' || err.code === 'timeout') && attempt < tries) {
await new Promise((r) => setTimeout(r, 500 * attempt));
continue;
}
throw err;
}
}
}
const finn = new FinnClient({ url: process.env.FINN_URL, apiKey: process.env.FINN_API_KEY });
await finn.connect();
const { text } = await askWithRetry(finn, 'How many active members do I have?');
console.log(text);
await finn.disconnect();