Skip to main content

Authentication

401 / missing key. Send X-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

422 invalid_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 in queued / 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 using Retry-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’s session_id. Do not reuse one session across a suite of unrelated questions.

SDK parity

TypeScript does not wrap search.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.