Skip to main content
The NDI CLI (ndi) gives you terminal access to the same /v1 methods as the SDKs: parse, extract, split, classify, ground, plus workspaces, ingestion, and search. Use it for batch processing, scripting, CI/CD, and quick operations without writing application code.

Fast path for coding agents

Use the CLI when the document is already on disk or when an agent needs a terminal-first workflow. It avoids writing upload code and prints Markdown that agents can read directly.
A directory is scanned recursively for supported files, one job each:
For agent tool calling instead of terminal commands, use the MCP server.

Installation

Requires Python 3.11+. The executable is ndi. ndi version prints the CLI and the ndi-sdk it is talking through.

Authentication

This opens the NDI console in the browser and writes the minted key to ~/.ndi/config.toml. $NDI_API_KEY still wins over the file. $NDI_CONFIG_PATH overrides the config location. To paste a key instead of opening a browser:
Workspace-scoped verbs also need a workspace:
The default host is https://ndi-api.nace.ai. Override with $NDI_BASE_URL.

Quick examples

Parsed markdown goes to stdout by default. Status lines (job <id> queued, saved …) go to stderr. -o auto|md|json|payload|id picks the shape. --json is an alias for -o json. --save PATH / --out-dir DIR write files. --async submits and prints the job id. When a Parse result has external complete content, -o auto and -o md print the inline preview. Use -o json or -o payload to read its disclosure and authenticated full-content URL. A final-size preview carries content_truncated; reduced-layout Office parsing carries parse_fidelity. Documents use document.content_url; workbooks use the affected document.spreadsheet.sheets[].content_url. The CLI does not download these URLs automatically. API failures exit 1. Usage and config errors exit 2. An unsupported single file is reported on stderr as error: 422 [unsupported_file_type] ...; the command does not print an upload handle or job ID, ndi files upload leaves no workspace file, and --ingest does not queue ingestion. A directory upload skips those files with a warning instead of failing the run.

Supported file types

Directory scans for document operations pick up these extensions (other files are skipped with a note on stderr). ndi files upload DIR also accepts the workspace-only types in Format support (HTML, plaintext, markdown, email, extra media). Per-operation gates are stricter and are enforced by the API for each file. Do not copy this table into automation as a permanent allowlist. Commands accept a file, a directory, an https:// URL, a staged ndi://upload/<uuid>, a prior parse jobid://<uuid> (not classify), a workspace file ws://<file_id>, or - (a handle line, or raw bytes with --file-name).

Parse

Converts a document into markdown, text, and layout blocks.

Flags

Examples

See Parse for what the result contains.

Extract

Pulls structured data against a JSON Schema you provide. The top-level schema must be an object. Pass exactly one of -s/--schema (file path or inline JSON) or --schema-id (a saved schema).
--validate checks the schema without creating a job. --instructions adds guidance. --no-citations drops cell/page citations. -p limits pages.
Extraction can reuse a prior parse: ndi parse a.pdf -o id | ndi extract - -s schema.json sends the job id on stdin instead of uploading the file again.
See Extract and Extraction schemas.

Split

Finds logical sections against classes you name. Each --class is id:label[:description]; --classes FILE.json takes an array of the same shape.
--unknown is include (default), force, or error.

Classify

Labels a document or its pages against your classes. Same --class / --classes shape as split.
--granularity is document (default) or page. --unknown is allow (default) or force_best. Classify cannot reuse a parse job — pass a file, URL, upload, or workspace file.

Ground

Locates quoted text back in the source.
Each --target is id=TEXT. --targets takes a JSON array of the same objects the API accepts.

Workspaces and files

A workspace is a persistent corpus. Upload, ingest, then search.
A directory walks nested files, keeps relative paths under --path (default: the directory name), and uploads in parallel (-j, default 8). Unsupported files and .DS_Store are skipped; a warning on stderr lists them when the run finishes. --ingest then queues ingestion in batches of 1000 file ids. A single file still requires --path. --on-conflict is reject (default) or new_version. Delete a workspace with --confirm-name equal to the workspace name. See Workspaces and Ingest.

Search and journal-entry testing

Files must be ingested first. These verbs return jobs.
hybrid-search is inline (no job). automatic-search, fact-search, deep-search, filtered-search, and je-testing wait for the job unless you pass --async. automatic-search prints the chosen route and the output job id; receipts and a filtered cursor belong to that job. A filtered route’s next: cursor pages with ndi filtered-search --cursor. ndi ledger-understanding start waits the same way; get is a read and prints the stored report. --effort on deep-search is low, medium, or high. JET uses --reasoning-effort to change its thinking budget without switching engines; omit it for the service default. --path on je-testing is repeatable and intersects with --prefix. There is no --session-id: a shell has nowhere to keep a thread. filtered-search answers filter/rank/count questions over the metadata catalog rather than by reading files. Its result prints a next: cursor when more rows remain; page it with ndi filtered-search --cursor <cursor>. A page is a new job that re-executes the original query against the current catalog, so --prefix and --context are refused with a cursor — they belong to the initial query — and the page’s own flags supersede the first run’s. There is no --result-unit (the server deprecates the grouping override) and no --allow-clarification (a clarification is answered by a new initial query, and a shell has nowhere to keep the thread). Read not exhaustive before treating a filtered-search count as a census: the matches are valid, but the total is a floor. An unreadable or partly readable line names the field behind a thin result. Neither exits non-zero — both are successful partial answers. A degraded journal-entry testing run exits non-zero even though the job succeeded — the procedure broke before finalizing an answer. --force on ledger-understanding start re-measures even when a report for this package already exists. Without it, an unchanged package republishes the stored report. A workspace that has never produced a report exits on ledger_understanding_not_found. See Search, Automatic search, Filtered search, Journal-entry testing, and Ledger understanding.

Workspace tools

Read-only inspection of an ingested corpus. These are the six verbs an agent sandbox is allowed to run; every other ndi command is refused there. See File tools and Query tables.

Jobs

-o id prints only the job id. A wait that times out still prints the id so you can ndi job it later. Default HTTP timeout is 900 seconds (--timeout). See Jobs.

Output

Next steps

Parse guide

Modes, pages, spreadsheets, and what the result contains.

Extract guide

JSON Schema extraction and citations.

Search a workspace

Hybrid, fact, deep search, and journal-entry testing.

MCP server

The same catalog as tools inside Claude Code, Cursor, or opencode.