---
title: "JavaScript SDK internals: constructor options, headers, retries and timeouts, env vars, logging, gateways, forward compatibility, migration notes"
type: reference
tags: [javascript, typescript, sdk, internals, logging, gateways]
created: 2026-09-23
updated: 2026-09-23
confidence: high
sources:
  - raw/docs/sdk__javascript.md
  - raw/docs/sdk__javascript__api.md
  - raw/docs/sdk__javascript__api__classes__TypeSafeClient.md
  - raw/docs/sdk__javascript__api__classes__APIPromise.md
  - raw/docs/sdk__javascript__api__interfaces__Models.md
  - raw/docs/sdk__javascript__api__interfaces__Logger.md
  - raw/docs/sdk__javascript__api__interfaces__SystemOneRequest.md
  - raw/docs/sdk__javascript__api__interfaces__RequestOptions.md
  - raw/docs/sdk__javascript__api__variables__VERSION.md
  - raw/docs/sdk__javascript__api__variables__LOG_LEVELS.md
  - raw/docs/sdk__javascript__api__variables__ENV.md
  - raw/docs/sdk__javascript__changelog.md
  - raw/docs/sdk__python__usage.md
  - raw/github/typesafe-sdk-js/README.md
  - raw/github/typesafe-sdk-js/package.json
  - raw/github/typesafe-sdk-js/jsr.json
  - raw/github/typesafe-sdk-js/examples/demo.ts
  - raw/github/typesafe-sdk-js/src/index.ts
  - raw/github/typesafe-sdk-js/src/client.ts
  - raw/github/typesafe-sdk-js/src/questions.ts
  - raw/github/typesafe-sdk-js/src/api-promise.ts
  - raw/github/typesafe-sdk-js/src/retry.ts
  - raw/github/typesafe-sdk-js/src/logging.ts
  - raw/github/typesafe-sdk-js/src/env.ts
  - raw/github/typesafe-sdk-js/src/runtime.ts
  - raw/github/typesafe-sdk-js/src/version.ts
  - raw/github/typesafe-sdk-js/src/resources/models.ts
  - raw/github/typesafe-sdk-js/test/client.test.ts
jev_version: "jev-1.13.0"
sdk_js: "0.6.0"
summary: "@typesafe-ai/sdk 0.6.0 internals: package facts, exports, full constructor options, headers, retries/timeouts, APIPromise, models, env vars, logging, gateways, forward compatibility, migration."
---

# JavaScript SDK internals: constructor options, headers, retries and timeouts, env vars, logging, gateways, forward compatibility, migration notes

> **TL;DR** The detail behind the builder contract on [[reference/javascript-sdk]]: package facts and entry points (ESM, CJS, JSR), the export list, every `TypeSafeClientConfig` option with verbatim construction errors, instance properties, the headers and wire body the client sends, per-attempt timeouts and retries, `APIPromise` in full, the `models` resource, TypeScript generics, `ENV` / `VERSION`, logging and redaction, pointing `baseURL` at an AI gateway, forward-compatibility escape hatches, and 0.5.7 → 0.6.0 migration. Documents `@typesafe-ai/sdk` 0.6.0. If you just need to make a call, read [[reference/javascript-sdk]] instead.

## Package facts

| Fact | Value | Source |
|---|---|---|
| npm name | `@typesafe-ai/sdk` | `package.json` |
| version | `0.6.0` | `package.json`, `src/version.ts` (`VERSION`) |
| description | "TypeScript SDK for the TypeSafe API" | `package.json` |
| license | MIT | `package.json`, `jsr.json` |
| author | `evinism` | `package.json` |
| engines | `node >= 20` | `package.json`; docs say "Node.js 20 or newer" |
| module type | `"type": "module"` (ESM-first) | `package.json` |
| homepage | `https://docs.typesafe.ai/sdk/javascript` | `package.json` |
| repository | `https://github.com/typesafe-ai/typesafe-sdk-js` | `package.json` |
| issues | `https://github.com/typesafe-ai/typesafe-sdk-js/issues` | `package.json` |
| published files | `dist`, `LICENSE`, `README.md` | `package.json` |
| `sideEffects` | `false` (tree-shakeable) | `package.json` |
| packageManager | `npm@11.19.0` | `package.json` |

### Entry points (ESM + CJS + declarations)

`package.json` declares dual exports. The README and docs both state: "The package includes ESM, CommonJS, and TypeScript declarations."

| Condition | File |
|---|---|
| `import` → types | `./dist/index.d.mts` |
| `import` → default | `./dist/index.mjs` |
| `require` → types | `./dist/index.d.cts` |
| `require` → default | `./dist/index.cjs` |
| legacy `main` | `./dist/index.cjs` |
| legacy `module` | `./dist/index.mjs` |
| legacy `types` | `./dist/index.d.cts` |
| subpath | `./package.json` only |

ESM:

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

CommonJS:

```js
const { choice, noul, score, TypeSafeClient } = require("@typesafe-ai/sdk");
```

There is no deep-import subpath: everything is exported from the package root (`src/index.ts` is the single entry).

### JSR

The repo contains a `jsr.json` and npm scripts `push:jsr` / `push:jsr:dry`, so the package is set up for JSR publication:

```json
{
  "name": "@typesafe-ai/sdk",
  "version": "0.6.0",
  "license": "MIT",
  "exports": "./src/index.ts",
  "publish": {
    "include": ["src/**/*.ts", "README.md", "LICENSE", "jsr.json"],
    "exclude": ["src/**/*.test.ts"]
  }
}
```

The JSR entry point is the TypeScript source (`./src/index.ts`), not `dist/`. Whether a JSR release actually exists on jsr.io is **not confirmed by any source in `raw/`**; only npm releases are recorded (see [[reference/javascript-sdk-changelog]]).

## What the package exports

From `src/index.ts` (value exports unless marked *type-only*):

| Export | Kind | Page |
|---|---|---|
| `TypeSafeClient` | class | [[reference/javascript-sdk]], this page |
| `APIPromise` | class | this page |
| `choice`, `score`, `noul` | functions | [[reference/javascript-sdk]] |
| `ENV` | const object | this page, [[reference/environment-variables]] |
| `LOG_LEVELS` | const array | this page |
| `VERSION` | const string `"0.6.0"` | this page |
| `TypeSafeError`, `APIError`, `APIConnectionError`, `APITimeoutError`, `APIUserAbortError`, `AuthenticationError`, `BadRequestError`, `InternalServerError`, `NotFoundError`, `PermissionDeniedError`, `RateLimitError`, `UnprocessableEntityError` | classes | [[reference/javascript-sdk-errors]] |
| `WithResponse` | *type-only* | [[reference/javascript-sdk-types]] |
| `EnvVar` | *type-only* | [[reference/javascript-sdk-types]] |
| `Models` | *type-only* (`export type { Models }`) | this page |
| everything in `src/types.ts` | *type-only* (`export type * from "./types"`) | [[reference/javascript-sdk-types]] |

`Models` is exported as a **type only**, so you cannot `new Models(...)` from the package; you reach it through `client.models`. (The published API reference lists it under "Interfaces"; in source it is a `class`; see the version notes at the bottom.)

## Constructor in full

```ts
new TypeSafeClient(config?: TypeSafeClientConfig): TypeSafeClient;
```

"Client for the TypeSafe AI API." Explicit options take precedence over environment variables, then SDK defaults. Empty or whitespace-only environment values are ignored (`readEnv` trims and treats blank as absent). Throws `TypeSafeError` when the API key is missing, configuration is invalid, or the runtime is unsupported.

Full property table for `TypeSafeClientConfig` (all properties optional):

| Property | Type | Required | Default | Description |
|---|---|---|---|---|
| `apiKey` | `string` | no (but required in effect) | `TYPESAFE_API_KEY` | Required API key; falls back to the env var. Missing → `TypeSafeError`. |
| `baseURL` | `string` | no | `TYPESAFE_BASE_URL`, then `https://api.typesafe.ai` | API root. Trailing slashes are stripped. |
| `defaultModel` | `string` | no | `TYPESAFE_DEFAULT_MODEL`, then `jev-latest` | Model used when a request omits `model`. |
| `logLevel` | `LogLevel` | no | `TYPESAFE_LOG_LEVEL`, then `warn` | `info` logs request summaries; `debug` adds headers and bodies. Known credential headers are redacted; **bodies are not**. |
| `logger` | `Logger` | no | prefixed `console` (`[typesafe-sdk]`) | Logger filtered to `logLevel` and above. |
| `retry` | `Partial<RetryPolicy>` | no | `DEFAULT_RETRY_POLICY` | Omitted fields use the `RetryPolicy` defaults. |
| `timeout` | `number` (ms) | no | `10000` | Timeout **per attempt**; there is no total retry budget. Must be a positive finite number. |
| `defaultHeaders` | `Record<string, string>` | no | `{}` | Additional request headers; per-call headers take precedence. |
| `dangerouslyAllowBrowser` | `boolean` | no | `false` | Allow browser use, exposing the API key to page users. |
| `fetch` | `Fetch` | no | global `fetch` | Custom HTTP fetch implementation for transport configuration or tests. |

Construction-time failures, all `TypeSafeError` (messages verbatim from `src/client.ts`):

- Browser detected and `dangerouslyAllowBrowser` not set: "TypeSafeClient is running in a browser, which would expose your API key to anyone using the page. Call the API from a server instead, or pass `dangerouslyAllowBrowser: true` if you understand the risk." Detection (`isBrowser()` in `src/runtime.ts`) checks for `window.document` and `navigator`.
- No key: "No API key was provided. Pass `apiKey` to the TypeSafeClient constructor or set the TYPESAFE_API_KEY environment variable."
- No global fetch and no `fetch` option: "No global `fetch` is available in this runtime. Pass a `fetch` implementation to the TypeSafeClient constructor."
- Invalid `timeout`: "`timeout` must be a positive number of milliseconds, got X."

### Instance properties

All are `readonly`. The API key is stored in a private field (`#apiKey`) and is not a public property.

| Property | Type | Description |
|---|---|---|
| `baseURL` | `string` | API root with trailing slashes removed. |
| `defaultModel` | `string` | Model used when a request omits `model`. |
| `logLevel` | `LogLevel` | Configured log verbosity. |
| `logger` | `Logger` | The configured logger, filtered to `logLevel`. |
| `retry` | `RetryPolicy` | Retry settings with constructor overrides applied (fully resolved, not partial). |
| `timeout` | `number` | Timeout per attempt in milliseconds. |
| `defaultHeaders` | `Readonly<Record<string, string>>` | Additional headers sent with each request. |
| `fetch` | `Fetch` | HTTP fetch implementation. |
| `models` | `Models` | The models available to the account. |

## `systemOne()` internals

Upstream example (JSDoc in `src/client.ts`):

```ts
const { answers } = await client.systemOne({
  state: "I was charged twice. Please help.",
  questions: { billing: noul("Is this about billing?") },
});
console.log(answers.billing.noul);
```

Wire behaviour: the SDK validates questions locally, then `POST`s `{...request, model: request.model ?? client.defaultModel}` (a `SystemOneRequestPayload`) to `POST /v1/systemone`. Additional properties on a request *variable* are forwarded, including `null` values (see [Forward compatibility](#forward-compatibility)). See [[reference/http-api]].

### Question validation

Builder-time throws (`TypeSafeError`, from `src/questions.ts`):

- `choice()` given an array: "Choice criteria must be a map of labels to descriptions, not a list."
- `score()` given an object: "Score criteria must be a list of descriptions indexed by score from zero, not a map."

`systemOne()` calls `validateQuestions` before sending. It throws `TypeSafeError` for:

- an empty object: "At least one question is required."
- a `score` question whose `criteria` is not an array: `Score question "<name>" has criteria that are not a list; score criteria must be a list of descriptions indexed by score from zero.`
- a `score` question with fewer than two criteria: `Score question "<name>" has N criteria; at least two scores are required.`

`choice` and `noul` questions are not otherwise validated client-side.

## Headers the client sends

Built in `fetchWithRetries`. User-supplied headers are merged first so they cannot clobber auth or the JSON content type; header matching is case-insensitive, last value wins. Per-call `headers` are merged over `defaultHeaders`.

| Header | Value |
|---|---|
| `Authorization` | `Bearer <apiKey>` |
| `Accept` | `application/json` |
| `User-Agent` | `typesafe-sdk/0.6.0` |
| `X-TypeSafe-SDK` | `typesafe-sdk/0.6.0` |
| `X-TypeSafe-Runtime` | e.g. `node/22.1.0 (darwin; arm64)`, `bun/<v>`, `deno/<v>`, `vercel-edge`, `cloudflare-workers`, `browser`, `unknown` |
| `Content-Type` | `application/json` when there is a body; omitted otherwise |
| `X-TypeSafe-Retry-Count` | absent on the first attempt; `"1"`, `"2"`, … on retries |

The response request ID is read from `x-typesafe-request-id`. **`defaultHeaders` cannot override auth**: `Authorization`, `Accept`, `User-Agent`, `X-TypeSafe-SDK`, `X-TypeSafe-Runtime`, and `Content-Type` are merged last and win.

## Retries and timeouts

`DEFAULT_RETRY_POLICY` (`src/retry.ts`): `maxRetries: 2`, backoff 500 ms doubling to 5000 ms with 0.25 jitter, retries HTTP 408, 429 and 500–599 plus `APIConnectionError` and `APITimeoutError`, honours `retry-after-ms` / `Retry-After` up to 60000 ms. `APIUserAbortError` is never retried. The full `RetryPolicy` and `RequestOptions` tables, the delay formula and the status-to-class map are on [[reference/javascript-sdk-errors]].

- **`timeout` is per attempt, not per call.** With `maxRetries: 2` and `timeout: 10000`, worst-case wall time is roughly three attempts plus backoff.
- **The body is fully buffered before the promise resolves**, under the same timeout, so a slow body counts against the attempt.
- A caller `signal` cancels the in-flight attempt and any pending retry wait; both surface as `APIUserAbortError`.
- Per-call `retry` inherits unset fields from the client's resolved policy; per-call `timeout` must also be a positive finite number.

Per-call overrides (built from the `RequestOptions` fields; not an upstream sample):

```ts
import { noul, TypeSafeClient } from "@typesafe-ai/sdk";

const client = new TypeSafeClient({ timeout: 5000, retry: { maxRetries: 1 } });
const controller = new AbortController();

const result = await client.systemOne(
  { state: "I was charged twice.", questions: { billing: noul("Is this about billing?") } },
  {
    timeout: 3000,
    retry: { maxRetries: 0 },
    headers: { "X-Request-Source": "support-bot" },
    signal: controller.signal,
  },
);
console.log(result.answers.billing.noul);
```

## `APIPromise` in full

`systemOne()` and `models.list()` return `APIPromise<T>`, a `Promise<T>` subclass. "Non-2xx responses reject with an `APIError`, including through `asResponse()`." The body is parsed lazily and at most once; `then`/`catch`/`finally` are overridden to go through that single parse.

| Method | Signature | Description |
|---|---|---|
| `asResponse()` | `(): Promise<Response>` | The raw `Response` without parsing the body. SDK requests buffer the full body under the request timeout before handoff; reading it afterwards is caller-owned. Don't also `await` the parsed result on the same promise. |
| `withResponse()` | `(): Promise<WithResponse<T>>` | `{ data, response, requestId }`: parsed result, HTTP response, and request ID from `x-typesafe-request-id`. Example on [[reference/javascript-sdk]]. |
| `map(fn)` | `<U>(fn: (data: T) => U): APIPromise<U>` | Transform the parsed result, sharing the HTTP response and a single body parse. |
| `then(onfulfilled?, onrejected?)` | overrides `Promise.then` | Parsed result. |
| `catch(onrejected?)` | overrides `Promise.catch` | — |
| `finally(onfinally?)` | overrides `Promise.finally` | — |

Constructor (public but intended for internal use): `new APIPromise<T>(responsePromise: Promise<Response>, parseResponse: (response: Response) => Promise<T>)`.

Because the body is buffered, `asResponse()` hands you a response whose body is already readable, but don't consume both `asResponse()` and the parsed value on the same `APIPromise`. **Response parsing is lenient**: bodies are `JSON.parse`d even when `content-type` is missing, falling back to the raw text, and an empty body parses to `undefined`.

## `models` resource

```ts
client.models.list(options?: RequestOptions): APIPromise<ModelCard[]>
```

"List the models available to the account." Calls `GET /v1/models` and unwraps the `{ models: [...] }` envelope; a response of any other shape raises `TypeSafeError` "Unexpected response shape from GET /v1/models; expected { models: [...] }."

`ModelCard`: `{ readonly name: string; readonly description: string; readonly release_date: string }`. Catalogue and pricing live in [[reference/models-and-pricing]].

```ts
const models = await client.models.list();
console.log(models.map((m) => `${m.name} (${m.release_date})`).join("\n"));
```

## TypeScript generics in practice

The `const` type parameter is what preserves literal label and tuple types, so you get narrowed keys without any manual annotation:

```ts
import { choice, score, TypeSafeClient } from "@typesafe-ai/sdk";
import type { ChoiceResponse, ScoreResponse, SystemOneResult } from "@typesafe-ai/sdk";

const client = new TypeSafeClient();

const questions = {
  tone: choice("What is the customer's tone?", { calm: null, angry: null }),
  urgency: score("How urgent is this?", ["can wait", "today", "right now"]),
} as const;

const result: SystemOneResult<typeof questions> = await client.systemOne({
  state: "My account is locked and I have a demo in ten minutes.",
  questions,
});

// `tone.choice` is "calm" | "angry", not string:
const tone: ChoiceResponse<{ calm: null; angry: null }> = result.answers.tone;
if (tone.choice === "angry") console.log("escalate");

// `urgency.probabilities` is keyed "0" | "1" | "2":
const urgency: ScoreResponse<readonly ["can wait", "today", "right now"]> = result.answers.urgency;
console.log(urgency.probabilities["2"], urgency.legend["2"]);
```

Writing questions inline in the `systemOne({ questions: { ... } })` call gives the same inference, because `systemOne` itself declares `<const Q extends Questions>`.

## Environment variables and `VERSION`

`ENV` maps config keys to env var names (see [[reference/environment-variables]]):

| `ENV` key | Env var | Effect |
|---|---|---|
| `ENV.apiKey` | `TYPESAFE_API_KEY` | Required API key; used when `apiKey` is omitted. |
| `ENV.baseURL` | `TYPESAFE_BASE_URL` | API root; defaults to `https://api.typesafe.ai`. |
| `ENV.defaultModel` | `TYPESAFE_DEFAULT_MODEL` | Default model name; defaults to `jev-latest`. |
| `ENV.logLevel` | `TYPESAFE_LOG_LEVEL` | Log level; defaults to `warn`. |

`VERSION` is the string literal type `"0.6.0"`, kept in sync with `package.json` and checked by `npm run check:version`.

## Logging

| Item | Value |
|---|---|
| `LOG_LEVELS` | `readonly LogLevel[]` = `["debug", "info", "warn", "error", "off"]`, most to least verbose |
| `LogLevel` | `"debug" \| "info" \| "warn" \| "error" \| "off"` (`off` disables logging) |
| default level | `warn` (`DEFAULT_LOG_LEVEL`) |
| default logger | `console` with the prefix `[typesafe-sdk]` |
| `Logger` | `{ debug, info, warn, error }`, each `(message: string, ...args: unknown[]) => void`; `console` satisfies it |

What gets logged: `info` emits per-attempt summaries (`#3 POST /v1/systemone <- 200 in 412ms (request req_…)`, timeouts, aborts, retry waits). `debug` additionally logs outgoing URL + headers + body and the parsed response body. Redaction covers `authorization`, `proxy-authorization`, `x-api-key` (masked to `Bearer ***abcd`, keeping the scheme and the last four characters of secrets longer than eight) and `cookie` / `set-cookie` (`***`). **Request and response bodies are not redacted**: do not use `debug` on production traffic containing personal data.

An invalid level from either the option or `TYPESAFE_LOG_LEVEL` throws `TypeSafeError`: `Invalid log level "X" from <source>. Expected one of: debug, info, warn, error, off.`

Routing SDK logs into your own logger (`myLogger` stands for your application's logger):

```ts
const client = new TypeSafeClient({
  logLevel: "debug",
  logger: {
    debug: (m, ...a) => myLogger.trace({ a }, m),
    info: (m, ...a) => myLogger.info({ a }, m),
    warn: (m, ...a) => myLogger.warn({ a }, m),
    error: (m, ...a) => myLogger.error({ a }, m),
  },
});
```

## Pointing the client at another base URL (AI gateways)

The JS docs do not document gateways. The mechanism is the same as in Python: the client sends to `` `${baseURL}/v1/systemone` `` and `` `${baseURL}/v1/models` `` (`src/client.ts`), and the constructor checks only that a key is present. The gateway values below are documented only in the Python usage guide (`raw/docs/sdk__python__usage.md`), which adds: "This requires the alternative API to follow the TypeSafe OpenAPI spec." Using them from JS is **(inferred, not tested)**.

| Gateway | `apiKey` | `baseURL` | `defaultModel` |
|---|---|---|---|
| OpenRouter | OpenRouter key (`OPENROUTER_API_KEY` in the Python sample) | `https://openrouter.ai/api` | `~typesafe/jev-latest` |
| Vercel AI Gateway | `AI_GATEWAY_API_KEY` | `https://ai-gateway.vercel.sh/typesafe` | `typesafe-ai/jev` |

```ts
import { noul, TypeSafeClient } from "@typesafe-ai/sdk";

const client = new TypeSafeClient({
  apiKey: process.env.OPENROUTER_API_KEY,
  baseURL: "https://openrouter.ai/api",
  defaultModel: "~typesafe/jev-latest",
});

const { answers } = await client.systemOne({
  state: "I was charged twice.",
  questions: { billing: noul("Is this about billing?") },
});
console.log(answers.billing.noul);
```

The `X-TypeSafe-*` headers above are sent to the gateway too. Neither gateway's pricing or rate limits are covered by [[reference/models-and-pricing]]; worked Python versions are on [[reference/python-sdk-internals]].

## Forward compatibility

| Need | Mechanism |
|---|---|
| Send a request field newer than the SDK | Put it on a request *variable* and pass that: "Additional properties on a request variable are forwarded, including `null` values" (docs; `test/client.test.ts` "forwards extra fields and null"). An inline object literal with an unknown key would hit TypeScript's excess-property check (inferred). |
| Send a question field newer than the SDK | The whole request object is `JSON.stringify`d as-is and `validateQuestions` checks only score criteria, so an extra key on a question object is sent (inferred from `src/client.ts`, not tested upstream). |
| Read an answer kind or field newer than the SDK | `systemOne()` does no runtime validation of the response body (the parsed JSON is cast to `SystemOneResult<Q>`), so unknown fields and answer types arrive untouched; the TypeScript types just do not describe them (inferred from `src/client.ts`). |
| Unexpected `GET /v1/models` shape | Only the `{ models: [...] }` envelope is checked; anything else throws `TypeSafeError`. |

```ts
import { score, TypeSafeClient } from "@typesafe-ai/sdk";

const client = new TypeSafeClient();
const request = {
  state: "s",
  questions: { q: score("?", ["low", "high"]) },
  future_option: null,
  nested: { enabled: true },
};
await client.systemOne(request); // body is { ...request, model: "jev-latest" }
```

The request object above is the one `test/client.test.ts` uses; the test then confirms a later call without the extra keys does not send `future_option`.

## Migrating from 0.5.7

The only recorded change in 0.6.0 is breaking: "accept `Score.criteria` as an ordered sequence instead of a dictionary keyed by integers". Replace `score(q, {0: "...", 1: "..."})` with `score(q, ["...", "..."])`; index 0 stays the lowest level, so `score`, `legend` and `probabilities` keep their meaning. Before/after code: [[reference/javascript-sdk-changelog]].

## CommonJS example

`examples/demo.ts` (full ESM version on [[reference/javascript-sdk]]) uses top-level `await`, so it must run as ESM. The CommonJS equivalent wraps the body in an async IIFE:

```js
const { APIError, choice, noul, score, TypeSafeClient } = require("@typesafe-ai/sdk");

(async () => {
  const client = new TypeSafeClient({ logLevel: "info" });
  try {
    const { answers, usage } = await client.systemOne({
      state: { subject: "Charged twice this month", body: "Two $49 charges in August." },
      questions: {
        isBilling: noul("Is this ticket about billing?"),
        sentiment: choice("What is the customer's tone?", {
          calm: null,
          frustrated: null,
          angry: null,
        }),
        urgency: score("How urgent is this ticket?", ["can wait", "this week", "today", "right now"]),
      },
    });
    console.log(answers.isBilling.noul, answers.sentiment.choice, answers.urgency.score);
    console.log(usage.input_tokens, usage.output_tokens);
  } catch (err) {
    if (err instanceof APIError) {
      console.error(`API error ${err.status} (request ${err.requestId ?? "unknown"}):`, err.body);
    } else {
      throw err;
    }
  }
})();
```

## Version notes

- This page describes `@typesafe-ai/sdk` 0.6.0 (repo commit `66880ccded6cb642dc1809620c2b108c33730214`, 2026-09-15).
- **Doc-vs-source discrepancy:** the published API reference lists `Models` under *Interfaces* (`# Interface: Models`), but `src/resources/models.ts` declares `export class Models` with a constructor taking an internal `Transport`. Because `src/index.ts` re-exports it with `export type { Models }`, only the type is importable: the docs' classification is accurate from a consumer's point of view, but the source is a class.
- The docs' `ChoiceCriteria` index signature renders as `[label: string]: EntryType`; source writes `[label: string]: Description`, and `Description = EntryType`, so they are the same type.
- 2026-09-23: this page was split out of [[reference/javascript-sdk]], which now carries only the builder contract.

## Related

- [[reference/javascript-sdk]] — the builder contract: install, client, `systemOne()`, builders, answers, errors
- [[reference/javascript-sdk-types]] — every interface and type alias in detail
- [[reference/javascript-sdk-errors]] — error classes, `RetryPolicy`, `RequestOptions`
- [[reference/javascript-sdk-changelog]] — release history and the 0.6.0 migration code
- [[reference/environment-variables]] — `TYPESAFE_*` across SDKs
- [[reference/http-api]] — the wire contract behind `systemOne()` and `models.list()`
- [[reference/python-sdk-internals]] — the Python equivalent, with the documented gateway samples

## Sources

- raw/docs/sdk__javascript.md (https://docs.typesafe.ai/sdk/javascript)
- raw/docs/sdk__javascript__api.md (https://docs.typesafe.ai/sdk/javascript/api)
- raw/docs/sdk__javascript__api__classes__TypeSafeClient.md (https://docs.typesafe.ai/sdk/javascript/api/classes/TypeSafeClient)
- raw/docs/sdk__javascript__api__classes__APIPromise.md (https://docs.typesafe.ai/sdk/javascript/api/classes/APIPromise)
- raw/docs/sdk__javascript__api__interfaces__Models.md, __Logger.md, __SystemOneRequest.md, __RequestOptions.md
- raw/docs/sdk__javascript__api__variables__VERSION.md, __LOG_LEVELS.md, __ENV.md
- raw/docs/sdk__javascript__changelog.md (https://docs.typesafe.ai/sdk/javascript/changelog)
- raw/docs/sdk__python__usage.md (https://docs.typesafe.ai/sdk/python/usage.md) — gateway values
- raw/github/typesafe-sdk-js (https://github.com/typesafe-ai/typesafe-sdk-js, commit 66880ccded6cb642dc1809620c2b108c33730214): README.md, package.json, jsr.json, examples/demo.ts, src/index.ts, src/client.ts, src/questions.ts, src/api-promise.ts, src/retry.ts, src/logging.ts, src/env.ts, src/runtime.ts, src/version.ts, src/resources/models.ts, test/client.test.ts
