TypeScript
The TypeScript SDK is a client for a running Curva server (curva serve, or a shared server). It
has zero dependencies and uses the global fetch, so it runs on Node 18+, Deno, Bun and in
browsers.
npm install curva-aiThe package is curva-ai on npm.
First decision
Section titled “First decision”import { Curva, choice, score, noul } from "curva-ai";
const curva = new Curva(); // CURVA_BASE_URL (default http://localhost:7777), CURVA_API_KEYconst d = await curva.decide( { ticket: "I was charged twice for order A-104. Please refund the duplicate!" }, { team: choice("Which team should handle this?", { billing: "payments, refunds", technical: "bugs", sales: "pricing" }), frustration: score("How frustrated is the customer?", ["calm", "annoyed", "angry"]), refund: noul("The customer explicitly asks for a refund"), },);
d.answers.team.choice; // "billing" | "technical" | "sales" | "none_of_these"d.answers.team.confidence;d.answers.frustration.score;d.answers.refund.noul; // P(yes)Answers are typed from the questions you pass: d.answers.team.choice is a union of that
question’s option keys (plus "none_of_these"), so a typo is a compile error.
Questions
Section titled “Questions”choice(instructions, options, { escape?, minConfidence?, examples?, think? })score(instructions, levels, { minConfidence?, examples?, think? })noul(instructions, { examples?, think? })multi(instructions, options, { threshold?, think? })dependsOn(question, ...keys) // answered after `keys`, seeing their answers; answers get `stage`options is a list of keys or a { key: description } object. think: true lets that one
question reason in a call of its own; dependsOn runs a workflow in one call.
Options
Section titled “Options”new Curva({ baseUrl, apiKey, model, timeout: 30_000, maxRetries: 3, headers, fetch });
await curva.decide(state, questions, { model: ["model-a", "model-b"], // or one of: council: ["model-a", "model-b"], // blended; answers get `agreement` cascade: ["cheap", "strong"], escalateBelow: 0.8, // answers get `answeredBy` race: ["model-a", "model-b"], // first valid answer wins mode: "auto", debias: true, project: "support", explain: true, config: "curva-1.0.0", privacy: "strict",});Decisions use camelCase: latencyMs, costUsd, cached, requestId (the x-request-id
header, for matching server logs).
Feedback and reports
Section titled “Feedback and reports”await curva.feedback(d.id, "team", "billing");await curva.calibration("team", "support");await curva.audit({ project: "support", limit: 100 });await curva.drift("team", { project: "support", weeks: 8 });await curva.models();await curva.health();Errors
Section titled “Errors”429, 5xx and dropped connections are retried, honouring Retry-After. Anything else throws a
CurvaError (with status, type and message), or one of its subclasses: AuthError,
InvalidRequestError, NotFoundError, RateLimitError and ModelError.

