Skip to main content
The NDI TypeScript SDK is a typed interface to the /v1 Platform API. It resolves credentials, mints idempotency keys, retries transient failures, and parses responses into TypeScript models.

Quick start


Key features

Typed end to end

Typed request options and response models. Unknown result variants load as UnknownResult, so an additive server change does not prevent the job from loading.

One async client

Every method returns a Promise. Share one NdiClient across requests and use Promise.all to collect concurrent work.

Jobs, not polling loops

Slow operations return a Job. Wait with jobs.wait, follow progress with jobs.events, or finish inline with wait_seconds.

Automatic retries

Backoff with jitter for 429, 5xx, and dropped connections. Job-creating calls mint idempotency keys so retries resolve to the original job.

Typed error hierarchy

Status failures expose the status code, error code, request ID, and retryability; job failures carry the terminal Job.

Zero runtime dependencies

Uses native fetch, FormData, and Blob. The package ships dual ESM and CommonJS builds and requires Node.js 20 or newer.

Installation

Requires Node.js 20+. The package is ndi-sdk on npm. To upgrade, see Update the SDKs, CLI, and MCP.

Authentication

Set your API key as an environment variable:
The SDK reads it automatically:
Or pass it explicitly:
With neither, the constructor raises Error before the first request. base_url defaults to process.env.NDI_BASE_URL, then https://ndi-api.nace.ai; set it only to reach another host. Browser applications must pass credentials explicitly and should not expose a long-lived API key in client-side code.

Client options

The option names use the SDK’s wire-style snake_case.

Core methods

Upload

Stage bytes for a granular operation. The handle is single-use and expires.

Upload documentation

Node and browser inputs, TTL, and when to use a workspace instead.

Parse

Turn a file into markdown, blocks, and optional chunks.

Parse documentation

Parse modes, OCR, figures, chunking, and spreadsheet options.

Extract

Fill a JSON Schema from a document. Pass json_schema or a stored schema_id.

Extract documentation

Schema design, field status, and citations.

Split

Cut a multi-document packet into classified segments.

Split documentation

Classes, unknown and overlap policy, and split rules.

Classify

Label a whole document, or each page, against classes you define.

Classify documentation

Criteria, subclasses, granularity, and unknown policy.

Ground

Pin quoted text to locations in the source document.

Ground documentation

Targets, match methods, and location shapes.

Workspaces

Build a durable corpus, then search it. Uploading registers a file; ingestion is a separate, billable operation.

Workspaces documentation

Workspaces, files, ingestion, tools, and search.

Automatic search and QA

Ask one question with search.automatic, or hold a conversation with search.qa. Both return a Job.
search.fact and search.deep are deprecated (@deprecated): they keep working until they are retired. See Search.

Automatic search

Routes, modes, structured answers, and catalog tables.

QA

Conversations, follow-ups, and clarifying questions.

Journal-entry testing

An audit procedure over an ingested ledger package, not a search preset. Returns a Job whose result is a JetResult. effort is the one knob; omit it to take the deployment’s own floor.

Journal-entry testing

Effort, file scope, SQL receipts, and figures.

Ledger understanding

Measures the ingested journal package — table roles, integrity, and optional GL parsing — and stores a report on the workspace. Returns a Job whose result is a LedgerUnderstandingResult.

Ledger understanding

Policy, reuse, the report, and in-progress conflicts.

Async and concurrency

Every method returns a Promise. Use Promise.all to fan out concurrent jobs and collect results together:
JobFailedError rejects out of Promise.all. Pass { raise_on_failure: false } to jobs.wait and handle job.status yourself if you want partial failures to settle rather than reject.
client.close() is a no-op because the SDK does not own the global or injected fetch. If a custom transport holds resources, close those resources through the transport that created them.

Async and concurrency guide

Client lifetime, bounded concurrency, and partial failures.

Result types and narrowing

Every document operation returns a Job. job.result is typed as JobResult | null — a discriminated union. Use the exported type guards to narrow to the concrete variant:
The SDK exports dedicated guards for ParseResult, UploadFileResult, and IngestionResult. Other known variants (split, extract, classify, ground, …) can be narrowed on result_type:
UnknownResult catches any result_type the server returns that this SDK version does not know. Exclude it with instanceof UnknownResult before reading variant fields.

Errors

Typed errors expose status_code, code, retryable, and request_id.

Error handling guide

The full exception hierarchy, retry policy, and request IDs.
See Errors.

Pagination

iterAll walks cursor pages for workspaces, files, jobs, and domains. Access labels use iterAccessLabels. See Pagination.

Advanced

Timeouts and retries

The SDK retries 429 and 5xx responses on idempotent requests (GET, PUT, DELETE, or any POST that carries an Idempotency-Key header — minted automatically by document operations). timeout applies per attempt, not over the full retry budget. Adjust wait_seconds when polling inline: timeout extends by wait_seconds for those requests.

Custom fetch and default headers

default_headers are merged onto every request. The X-API-Key header is always set by the SDK and cannot be overridden via default_headers.

SSE job events

Stream status updates as a job runs using the server-sent events endpoint:
Resume from a checkpoint with { last_event_id: event.sequence.toString() }.

Legacy client

The client.legacy namespace wraps the original /api/v1 routes for callers who have not migrated to /v1. Upload a local file with client.legacy.upload. It returns a legacy upload handle with file_id, file_name and expires_at, not the /v1 UploadResponse with upload_id. Pass the handle to client.legacy.parse:
toFileInput converts a source URL string or a legacy upload handle into a FileInput. A string is sent as source_url, so it must be an https://, s3://, or ndi://file/<id> URL, not a local path. Use the /v1 surface for new integrations.

Not wrapped

Call HTTP directly for these operations: effort is the knob on search.jeTesting only.

Next steps