Authentication
401 / missing key. SendX-API-Key: ndi_sk_…. Authorization: Bearer works
on hosted MCP only. Check $NDI_API_KEY is exported in the same shell as the
client.
Wrong host. Default base is https://ndi-api.nace.ai. Local stacks need
$NDI_BASE_URL. Paths are /v1/..., not /api/v1/....
See Authentication.
Invalid requests
422invalid_request. Extra JSON fields on a strict body — especially
wait_seconds or Idempotency-Key inside JSON. Those are a query parameter
and a header.
Classify with parse_result. Classify reads the original file. Upload or
workspace_file only.
qa_file on Excel. Use query tables.
Access labels as strings. Workspace access.labels is
[{ "name", "description" }, ...]. Names cannot contain hyphens.
Uploads
upload_expired. Granular POST /v1/uploads handles are single-use and
default to 24 hours (7-day cap). Create a new upload.
file_too_large. Use a workspace upload session
(SDK auto-chunks at 32 MiB) or tighten max_bytes on a grant.
path_conflict. A workspace path is taken. Pass on_conflict=new_version
or pick another path.
Browser 401 on grant redeem. Send X-Upload-Token, not your API key.
Ingestion and coverage
Search or QA returns nothing. Upload does not ingest. Call ingest, then retry.file_not_ingested is 409.
coverage.restricted_candidates > 0. Your key is missing labels listed in
labels_required. See Access labels.
Stale results after replace. Ingest with stale_only=True or run
reconcile.
Jobs
Stuck inqueued / running. Poll GET /v1/jobs/{id} or stream
/events. Cancel with POST /v1/jobs/{id}/cancel. job_not_cancellable means
it already finished.
result_expired. The job row remains; retention dropped the payload.
Re-run the operation. jobs.request expires with the same TTL.
zsh poll loop parse error. Compare STATUS with ==, not
=~ succeeded|failed|cancelled.
Rate and concurrency
429. Back off usingRetry-After if present. SDK clients retry idempotent
reads and replay-safe writes that carry an Idempotency-Key.
Slow batch. Share one AsyncNdiClient / NdiClient across tasks. Opening
a client per file exhausts the connection pool.
Search sessions
Deep search follow-up ignores context. Pass that turn’ssession_id. Do
not reuse one session across a suite of unrelated questions.
SDK parity
TypeScript does not wrapsearch.filtered, tools.run_sql, jobs.request,
or jobs.delete. Call those HTTP routes. Extraction schema CRUD is REST-only
in both languages.
See Errors and limits.