Skip to main content
The NDI Python SDK is a typed interface to the /v1 Platform API. It resolves credentials, mints idempotency keys, retries transient failures, and parses every response into a Pydantic model.

Quick start


Key features

Typed end to end

Pydantic models for every request and response. Unknown fields and unknown enum members load instead of raising, so an additive server change never breaks a client.

Sync and async

NdiClient and AsyncNdiClient expose identical namespaces. The sync client runs on httpx.Client directly rather than wrapping an event loop.

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 full jitter for 429, 5xx, and dropped connections. Job-creating calls carry an idempotency key, so a retry collapses onto the original job instead of billing twice.

Typed error hierarchy

Every failure is an NdiError subclass carrying the status code, error code, request ID, and retryability.

Two thin dependencies

httpx and pydantic only. The wire models are hand-written, so the package pulls in nothing from the NDI backend.

Installation

Requires Python 3.11+. The package is ndi-sdk on PyPI. 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 ValueError rather than letting the first call fail as a 401. base_url defaults to $NDI_BASE_URL, then https://ndi-api.nace.ai; set it only to reach another host.

Client options


Core methods

Upload

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

Upload documentation

Content types, 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. Give every class you expect to find; pages that match none are governed by unknown_policy.

Split documentation

Classes, unknown and overlap policy, and split rules.

Classify

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

Classify documentation

Granularity, unknown policy, and per-page labels.

Ground

Pin quoted text to a location in the source document.

Ground documentation

Targets, match methods, and location shapes.

Workspaces

Build a durable corpus, then search it. Uploading registers a ledger row; ingestion is a separate, billable call.

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, search.deep, and search.filtered are deprecated: they warn and 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 client

AsyncNdiClient has the same surface with await. Share one client across tasks.

Async and concurrency guide

Bounded concurrency, return_exceptions, and client lifetime.

Response types

documents.parse returns a Job, not a ParseResult — the work outlives the HTTP request, so the result arrives on the job. Once the job succeeds, job.result is a ParseResult. Each method has one result model, tagged by result_type:
Because Job is one model for every kind of work, its result field is typed as the union of all result models. That is invisible at runtime, but a type checker will ask you to narrow before reading variant fields:
A result whose tag this SDK version does not know arrives as an UnknownResult holding the raw payload, so a newly added job kind is never an outage.

Pagination

iter_all walks cursor pages for workspaces, files, jobs, and domains; access labels use iter_access_labels. See Pagination.

Error handling

Catch NdiError for anything the SDK raises, or a specific subclass when you plan to act on it.

Error handling guide

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

Advanced usage

Timeouts and retries

timeout bounds one HTTP request, not a job. Waiting for work is jobs.wait(...).

Custom HTTP client

Bring your own httpx.Client for proxies, mTLS, connection tuning, or a mock transport in tests. When you pass one, you close it.

Default headers

Streaming job progress

jobs.events yields Server-Sent Events until the job is terminal. Treat it as a latency convenience and confirm the final state with jobs.get or jobs.wait.

Legacy API surface

client.legacy exposes the older /api/v1 product API. It is imported on first use, so a process that never touches it does not pay for its models.

Next steps

1

Get an API key

Create a key and export it as NDI_API_KEY. See Authentication.
2

Run the quickstart

Parse your first document end to end in the Quickstart.
3

Explore the methods

Start with Parse, then Job management. The same operations also run from the terminal — see NDI CLI.