TypeScript SDK
Call Drex's decision model from Node with drex-sdk — calibrated questions, retries and typed errors.
The drex-sdk npm package is the official TypeScript client for https://console.nace.ai. This page covers Drex's decision model: systemOne and models.list. The same package also covers Perception's document jobs, uploads and saved schemas — see the Perception SDK page for those. It needs Node 20 or later and ships as both ESM and CommonJS.
The client is built on the TypeSafe SDK, so systemOne, noul, choice, score, the inferred answer types, RetryPolicy and the error classes match @typesafe-ai/sdk. Code written for TypeSafeClient runs on DrexClient once you rename the import; see Migrate from TypeSafe.
npm install drex-sdk
export DREX_API_KEY="nace_sk_..."new DrexClient() reads DREX_API_KEY, DREX_BASE_URL, DREX_DEFAULT_MODEL and DREX_LOG_LEVEL. Values you pass to the constructor (apiKey, baseURL, defaultModel, logLevel) take precedence. The client refuses to run in a browser, because the key spends your account's credit. Create a key as shown in Authentication.
Ask questions
client.systemOne(params, options?)
| Parameter | Type | Required | Description |
|---|---|---|---|
state | string | object | array | null | Yes | Text, a JSON object or array, or null to evaluate. See State. |
questions | Record<string, Question> | Yes | Nonempty, keyed by the name used to identify its answer. Built with noul(), choice() or score(). See Questions. |
model | string | No | Model override. Default: the client's defaultModel, itself drex-latest unless set. |
import { choice, DrexClient, noul, score } from "drex-sdk";
const client = new DrexClient();
const { answers, usage, request_id } = await client.systemOne({
state: "I was charged twice for my March invoice.",
questions: {
wants_refund: noul("Is the customer asking for a refund?"),
topic: choice("Which topic is it?", { billing: null, shipping: null, other: null }),
urgency: score("How urgent is it?", ["low", "medium", "high"]),
},
});
answers.wants_refund.noul; // number
answers.topic.choice; // "billing" | "shipping" | "other"The answer types come from your questions, so a misspelled question name or label is a compile error. Pass model to override the client's default for one call. await client.systemOne(...).withResponse() also returns the raw Response and its request id. await client.models.list() lists the models you can use.
For client.documents.* (parse, split, classify, extract, ground), uploads, jobs and saved schemas, see the Perception SDK page.
Handle errors
Every error the SDK throws is a DrexError. HTTP failures throw an APIError subclass:
| Status | Class |
|---|---|
| 400 | BadRequestError |
| 401 | AuthenticationError |
| 402 | InsufficientCreditError (error.type is insufficient_credit or payment_required) |
| 403 | PermissionDeniedError |
| 404, 410 | NotFoundError |
| 409 | ConflictError |
| 422 | UnprocessableEntityError |
| 429 | RateLimitError |
| 5xx | InternalServerError (529 is OverloadedError) |
Each APIError has status, body, headers and requestId, plus Drex's type, code, issues and serverRetryable. A failure without a response throws APIConnectionError or APITimeoutError, and an aborted signal throws APIUserAbortError.
The client retries 408, 429 and 5xx responses, dropped connections and timeouts, up to 2 retries within a 30-second budget, with backoff from 500 ms doubling to 5 s. It honors retry-after-ms and retry-after, and it does not retry an error whose body says "retryable": false. A decision is billed only when it returns 200, so a retry never bills twice. To change the retry behavior or the per-attempt timeout (60 s by default):
const client = new DrexClient({
timeout: 90_000,
retry: { maxRetries: 4, backoffMaxMs: 10_000, timeoutMs: 120_000 },
});Each call also takes { signal, timeout, retry, headers } as its last argument. See Errors and retries for what each error means.