Get started

TypeScript SDK

Call Drex's decision model and run Perception's document tools from Node with nace-sdk — client options, calibrated questions, typed answers, parse, split, classify, extract, ground, uploads, jobs, workspaces, workspace files, domains, search, saved schemas, errors, retries and logging.

The nace-sdk npm package is the official TypeScript client for https://console.nace.ai. One client covers Drex's decision model, systemOne, decisions.create and models.list, and Perception's document tools: the five document jobs, uploads, jobs, job files, workspaces, workspace files, domains, storage connections, search and saved extraction schemas. 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 keep the names they have in @typesafe-ai/sdk. The SDK types take instructions and criteria as text (the API also renders JSON instructions, see Questions), and it has its own classes for 402 and 529; Migrate from TypeSafe lists every difference.

npm install nace-sdk
export NACE_API_KEY="nace_sk_..."

Create a key as shown in Authentication, and keep it on a server: it spends your account's balance, and a browser can't call the /v1 routes from another origin (see Browsers). The samples on this page use a client made with new NaceClient(), which reads NACE_API_KEY, NACE_BASE_URL, NACE_DEFAULT_DECISION_MODEL and NACE_LOG_LEVEL.

Configure the client

new NaceClient(config?)

ParameterTypeRequiredDescription
apiKeystringYes, or NACE_API_KEYYour nace_sk_ key.
baseURLstringNoAPI root, with trailing slashes removed. Default: NACE_BASE_URL, then https://console.nace.ai.
defaultModelstringNoThe model for calls that don't name one. Default: NACE_DEFAULT_DECISION_MODEL, then drex-latest.
timeoutnumberNoMilliseconds per attempt, more than 0. Default: 60000, because Drex waits up to 55 s for the model before it answers 529. A document create adds its wait_seconds to it.
retryPartial<RetryPolicy>NoRetry overrides. Fields you leave out keep the defaults in Retries.
logLevelLogLevelNo"debug", "info", "warn", "error" or "off". Default: NACE_LOG_LEVEL, then "warn". See Logging.
loggerLoggerNoWhere log lines go. Default: console, with a [nace-sdk] prefix.
defaultHeadersRecord<string, string>NoHeaders sent with every request to Drex. They aren't sent to the document service's upload URLs or to signed links.
clientInfostringNoThe application or agent using the SDK, as name/version, sent as X-Nace-Client so Nace can record it with each request, and appended to User-Agent after the SDK's own nace-sdk/<version>. Not sent to upload URLs or signed links.
fetchFetchNoThe fetch to send requests with, for a proxy agent or a test double. Default: the global fetch.
dangerouslyAllowBrowserbooleanNoLet the constructor run where the browser globals window, window.document and navigator are all defined. Default: false. See Browsers.

config is a NaceClientConfig. Fetch is (input: string, init?: RequestInit) => Promise<Response>, the shape of the global fetch.

An option you pass wins over its environment variable, and the variable wins over the default. Blank or whitespace-only environment values are ignored; an option you pass is used as it is, even when empty. The variables are read from process.env, so in a runtime without it, pass the options. The client doesn't read the CLI's ~/.nace/config.toml: pass apiKey, or set NACE_API_KEY.

VariableOptionENV key
NACE_API_KEYapiKeyENV.apiKey
NACE_BASE_URLbaseURLENV.baseURL
NACE_DEFAULT_DECISION_MODELdefaultModelENV.defaultModel
NACE_LOG_LEVELlogLevelENV.logLevel

Each variable's name from before the package was renamed from drex-sdk still works when the new one is unset or blank: DREX_API_KEY, DREX_BASE_URL, DREX_DEFAULT_MODEL and DREX_LOG_LEVEL (LEGACY_ENV). The new name wins when both are set.

The constructor throws a NaceError when neither apiKey nor NACE_API_KEY is set, when timeout or a retry field is out of range, when baseURL (or NACE_BASE_URL) is not an absolute URL or contains user information (user@), when the log level is unknown (from the option or from NACE_LOG_LEVEL), when the runtime has no global fetch and you passed none, and in a browser without dangerouslyAllowBrowser. The SDK counts a runtime as a browser when the globals window, window.document and navigator are all defined.

client.ts
import { NaceClient } from "nace-sdk";

const client = new NaceClient({
  timeout: 90_000,
  retry: { maxRetries: 4, timeoutMs: 500_000 }, // allow 5 attempts; opt into a 500 s budget for starting retries
  defaultHeaders: { "X-Team": "support" },
  logLevel: "info",
});

The SDK's X-Nace-SDK and X-Nace-Runtime remain the transport identity. A valid X-Nace-Client is recorded separately as client_app; a recognized application can determine the traffic channel without replacing SDK identity. No client declaration is sent to external upload or signed download URLs.

NACE_API_KEY and NACE_BASE_URL configure the key and API root for cloud, custom and on-prem deployments. No constructor arguments or login profile are required. Explicit arguments override the environment, and the legacy DREX_* names remain supported. The configured API root is trusted configuration: select the key and root for the same deployment. A URL with a user@ part is refused.

For an on-prem deployment, set the environment pair and use the client normally:

export NACE_API_KEY="nace_sk_..."
export NACE_BASE_URL="https://nace.customer.example"

new NaceClient() reads this configuration. Explicit apiKey and baseURL options are supported too. Server-side authenticated API and upload-grant requests follow same-origin redirects; a change of scheme, host or effective port is rejected. Credential-free signed links use native redirect following without API headers or cookies. A browser that hides authenticated redirect destinations cannot safely follow them.

Client properties

Every option resolves to a property typed readonly, so TypeScript refuses an assignment to it; JavaScript does not stop one at runtime. The API key is never exposed. A JavaScript change to baseURL selects a new trusted API root for subsequent calls.

PropertyTypeDescription
baseURLstringThe API root in use.
defaultModelstringThe model for calls that don't name one.
timeoutnumberMilliseconds per attempt.
retryRetryPolicyThe full retry policy, with your overrides applied.
logLevelLogLevelThe resolved log level.
loggerLoggerYour logger, or the console one, filtered to logLevel.
defaultHeadersReadonly<Record<string, string>>A copy of defaultHeaders.
fetchFetchThe fetch you passed, or by default a function that calls the global fetch.
modelsModelsList models.
decisionsDecisionsPOST /v1/decisions, OpenAI's Decisions shape on the same models and billing. See OpenAI-compatible decisions.
documentsDocumentsDocument jobs and uploads. See Create a job and Uploads.
jobsJobsReading, waiting for and deleting document jobs, and their files. See Jobs and Job files.
extractionSchemasExtractionSchemasSaved extraction schemas. See Saved schemas.
browserRunsBrowserRunsCloud browser runs. See Browser runs.
workspacesWorkspacesWorkspaces, with their files and ingestion. See Workspaces and Workspace files.
domainsDomainsThe taxonomies a workspace classifies its files against: create, edit, publish and duplicate them. See Domains.
storageConnectionsStorageConnectionsYour own S3 buckets and Azure Blob containers a new workspace can keep its files in. See Storage connections.
searchSearchQuestions over a workspace's files. See Search.

Browsers

new NaceClient() throws in a browser, because the page would hand your API key to anyone who opens it. dangerouslyAllowBrowser: true only turns that check off. It doesn't make Drex reachable: the /v1 routes send no Access-Control-Allow-Origin header, so the browser blocks a page on another origin from reading their answers, and every call fails with APIConnectionError (or with a NaceError when the response is a redirect the browser hides). The option is for code that has browser globals but no cross-origin checks, such as tests under jsdom.

Call Drex from your server. To move files in a browser, mint an upload grant on the server and let the browser send the file to the document service, and hand the browser signed links from jobs.fileLink; see Upload grants.

Per-call options

Every method takes a RequestOptions as its last argument. A few document methods take more, or less; their sections say so.

ParameterTypeRequiredDescription
signalAbortSignalNoCancels the request and any wait before a retry. Throws APIUserAbortError.
timeoutnumberNoMilliseconds per attempt, in place of the client's timeout. The retry budget decides whether another attempt starts; it doesn't shorten an attempt.
retryPartial<RetryPolicy>NoRetry overrides for this call. Fields you leave out keep the client's policy. { maxRetries: 0 } turns retries off.
headersRecord<string, string>NoExtra headers, merged over defaultHeaders. Names compare case-insensitively.

The SDK sets Authorization, Accept, User-Agent, X-Nace-SDK, X-Nace-Runtime, Content-Type, Idempotency-Key and, on a retry, X-Nace-Retry-Count itself, so a value you pass for one of them is replaced or dropped.

const controller = new AbortController();
setTimeout(() => controller.abort(), 10_000);

await client.systemOne(
  { state: "Where is my parcel?", questions: { shipping: noul("Is it about shipping?") } },
  { signal: controller.signal, retry: { maxRetries: 0 }, headers: { "X-Trace-Id": "t-123" } },
);

Document batches

Start with 8 concurrent documents, keeping each upload, create and wait in one worker. wait_seconds: 60 holds the create open for up to 60 seconds; jobs.wait polls with the SDK's delay if the job is still running. Job reads and upload setup each have their own RPM window, at 5x and 2x the create allowance respectively (including an account RPM override). Creates stay at 120/min free or 600/min paid; reads allow 600/3000 and upload setup 240/1200. They cannot spend the create budget, but can return their own 429; NDI can also throttle them. The current SDK honors Retry-After (seconds until retry) automatically, without a default total retry budget. See Rate limits.

batch.ts
import { openAsBlob } from "node:fs";
import { readdir } from "node:fs/promises";
import { NaceClient, workspaceFile } from "nace-sdk";

const client = new NaceClient();
const paths = (await readdir("invoices")).filter((name) => name.endsWith(".pdf")).sort();
let next = 0;
const failures: string[] = [];
await Promise.all(Array.from({ length: 8 }, async () => {
  while (next < paths.length) {
    const name = paths[next++];
    try {
      const file = await client.documents.upload(await openAsBlob(`invoices/${name}`), {
        file_name: name,
      });
      const job = await client.documents.extract({
        source: workspaceFile(file),
        schema: { type: "object", properties: { total: { type: "number" } } },
        wait_seconds: 60,
      });
      console.log(name, await client.jobs.wait(job.job_id));
    } catch (error) {
      failures.push(name);
      console.error(name, error);
    }
  }
}));
if (failures.length) throw new Error(`${failures.length} documents failed; inspect the per-file errors`);

On this page