SDK / TypeScript

Jev TypeScript SDK: install, call, and read typed answers.

Install @typesafe-ai/sdk, keep TYPESAFE_API_KEY on your server, then call TypeSafeClient.systemOne(). Questions are a keyed object; results are read from response.answers under those same keys.

Current package at verification: 0.6.0. Requires Node.js 20 or newer. Verified September 22, 2026 against the published npm package and official source.

npm@typesafe-ai/sdkTypeScript declarations included

Install and make the smallest useful request

Install the official package. It ships ESM, CommonJS, and its own TypeScript declarations; do not install a separate @types package.

Install
npm install @typesafe-ai/sdk

Set the API key in the environment where your server process runs. The zero-argument constructor reads it automatically.

Server environment
# .env.local or your deployment secret store
TYPESAFE_API_KEY=your_typesafe_api_key
Server only: Do not bundle a TypeSafe API key into browser code. The SDK refuses browser use by default because doing so would expose the credential to every visitor.
ask-jev.ts
import { choice, TypeSafeClient } from "@typesafe-ai/sdk";

// Reads TYPESAFE_API_KEY from the server environment.
const client = new TypeSafeClient();

const response = await client.systemOne({
  state: { message: "I was charged twice. Please fix this ASAP." },
  questions: {
    category: choice("Which team should handle this?", {
      billing: "Payments, invoices, or refunds",
      technical: "Product errors or technical problems",
      other: "Anything else",
    }),
  },
});

console.log(response.answers.category.choice);
// Type: "billing" | "technical" | "other"

This is asynchronous: systemOne() returns an awaitable APIPromise. Default model is jev-latest, so the minimal request does not need a model field.

Use Choice, Score, and Noul in one typed request

Builder helpers preserve question names, Choice labels, and Score tuple positions. That gives your editor useful types at the exact point where application code reads an answer.

ticket-triage.ts
import { choice, noul, score, TypeSafeClient } from "@typesafe-ai/sdk";

const client = new TypeSafeClient();

const result = await client.systemOne({
  state: {
    subject: "Charged twice this month",
    body: "I see two $49 charges. Please fix this today.",
    customerPlan: "business",
  },
  questions: {
    queue: choice("Which queue should own this ticket?", {
      billing: "Charges, invoices, refunds, or payment methods",
      support: "Product usage or account help",
      security: "Suspicious access or compromised credentials",
    }),
    urgency: score("How urgent is the ticket?", [
      "Can wait",
      "Needs attention this week",
      "Needs attention today",
    ]),
    refundRequested: noul("Does the customer request a refund?", {
      true: "The customer explicitly asks for money back",
      false: "The customer asks only for investigation or correction",
    }),
  },
});

const queue = result.answers.queue.choice;
//    ^? "billing" | "support" | "security"

const queueProbability = result.answers.queue.probabilities[queue];
const urgency = result.answers.urgency.score;
const refundProbability = result.answers.refundRequested.noul;

console.log({
  queue,
  queueProbability,
  urgency,
  refundProbability,
  model: result.model,
  inputTokens: result.usage.input_tokens,
});
QuestionRead the answerWhat TypeScript knows
Choiceanswers.queue.choiceUnion of your criteria keys; probabilities use the same keys
Scoreanswers.urgency.scoreNumber plus typed legend and probabilities for tuple indices
Noulanswers.refundRequested.noulNumber representing probability of yes, from 0 to 1
!Noul is not a boolean. Its noul field is the probability of yes. Choose and validate any action threshold on your own labeled workload; do not cast the number to boolean.

Exact request and response contracts

01Request: state accepts a string, JSON object, JSON array, or null. questions must be a non-empty object keyed by the answer names you want back. model is optional.
02Choice: choice(instructions, criteria) requires criteria as an object mapping labels to descriptions or null. It is not an array of choices.
03Score: score(instructions, criteria) requires an ordered array with at least two levels. Returned score is an expected numeric value and can fall between integer levels.
04Noul: noul(instructions, criteria?) accepts optional true and false descriptions. Its answer contains type: "noul" and noul.
05Result: model, keyed answers, and usage with input_tokens and output_tokens. Choice and Score also return confidence and probabilities; Score adds legend.

For conceptual guidance on when each primitive fits, use the Choice, Score, and Noul reference. This page owns only their TypeScript SDK shapes.

Handle API, connection, timeout, and cancellation failures

Reuse one client, keep calls behind your server boundary, and log the request ID from API failures. The SDK already retries eligible connection failures, timeouts, HTTP 408, 429, and 5xx responses by default; per-call options can narrow the policy and add cancellation.

safe-request.ts
import {
  APIConnectionError,
  APIError,
  APIUserAbortError,
  noul,
  TypeSafeClient,
} from "@typesafe-ai/sdk";

const client = new TypeSafeClient();

export async function checkTicket(message: string) {
  const controller = new AbortController();
  const cancelTimer = setTimeout(() => controller.abort(), 5_000);

  try {
    return await client.systemOne(
      {
        state: { message },
        questions: {
          needsReview: noul("Does this ticket require human review?"),
        },
      },
      {
        signal: controller.signal,
        timeout: 10_000,
        retry: { maxRetries: 2 },
      },
    );
  } catch (error) {
    if (error instanceof APIUserAbortError) {
      throw new Error("Jev request exceeded the application time budget", {
        cause: error,
      });
    }

    if (error instanceof APIError) {
      console.error("TypeSafe API error", {
        status: error.status,
        requestId: error.requestId,
        body: error.body,
      });
      throw error;
    }

    if (error instanceof APIConnectionError) {
      console.error("Could not reach TypeSafe", error);
      throw error;
    }

    throw error;
  } finally {
    clearTimeout(cancelTimer);
  }
}

APITimeoutError extends APIConnectionError. APIError exposes status, headers, body, and requestId. These are exported SDK classes, not application-defined placeholders.

Choose a model and configure the client

Model nameCurrent targetUse
jev-latestjev-1.13.0Default moving stable alias
jev-previewjev-1.13.0Newest release, including previews
jev-1.13.0PinnedReproducible evaluated behavior

Aliases can move. Log result.model, which reports the versioned model that answered. Pin jev-1.13.0 only when your deployment needs a stable version while you re-evaluate a newer release.

Constructor and environment precedence

Explicit constructor values win over environment variables, which win over SDK defaults. The official client supports apiKey, baseURL, defaultModel, timeouts, retries, logging, headers, and a custom fetch implementation.

client-config.ts
import { TypeSafeClient } from "@typesafe-ai/sdk";

const client = new TypeSafeClient({
  apiKey: process.env.TYPESAFE_API_KEY,
  baseURL: process.env.TYPESAFE_BASE_URL,
  defaultModel: process.env.TYPESAFE_DEFAULT_MODEL,
});

The matching variables are TYPESAFE_API_KEY, TYPESAFE_BASE_URL, and TYPESAFE_DEFAULT_MODEL. Direct TypeSafe defaults to https://api.typesafe.ai. Use a custom base URL only when the upstream provider documents it; see the dedicated Vercel or OpenRouter owner pages for provider-specific behavior.

Not affiliated with, endorsed by, or operated by TypeSafe AI. Vendor claims are cited and attributed.