curl --request POST \
--url https://ndi-api.nace.ai/v1/workspaces/{workspace_id}/deep-search \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"query": "<string>"
}
'{
"created_at": "2023-11-07T05:31:56Z",
"job_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"kind": "parse",
"status": "queued",
"deleted_at": "2023-11-07T05:31:56Z",
"effort": "<string>",
"error": {
"code": "invalid_request",
"message": "<string>",
"request_id": "<string>",
"retryable": true,
"detail": {}
},
"finished_at": "2023-11-07T05:31:56Z",
"force": true,
"idempotency_key": "<string>",
"name": "<string>",
"payload_expires_at": "2023-11-07T05:31:56Z",
"progress": {
"items_done": 0,
"items_failed": 0,
"items_total": 123,
"stage": "<string>",
"units_done": 0,
"units_total": 123
},
"project_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"query": "<string>",
"query_scope": {
"type": "workspace"
},
"result": {
"document": {
"page_count": 123,
"blocks": [
{
"content": "<string>",
"id": "<string>",
"type": "<string>",
"confidence": 123,
"image_url": "<string>",
"location": {
"page": 2,
"kind": "page_region",
"polygons": [
[
[
123,
123
]
]
]
}
}
],
"chunks": [
{
"content": "<string>",
"id": "<string>",
"location": {
"page": 2,
"kind": "page_region",
"polygons": [
[
[
123,
123
]
]
]
}
}
],
"markdown": "<string>",
"ocr": {
"lines": [
{
"bbox": {
"height": 123,
"left": 123,
"original_page": 123,
"page": 123,
"top": 123,
"width": 123
},
"confidence": 123,
"text": "<string>",
"chunk_index": 123,
"rotation": 0
}
],
"words": [
{
"bbox": {
"height": 123,
"left": 123,
"original_page": 123,
"page": 123,
"top": 123,
"width": 123
},
"confidence": 123,
"text": "<string>",
"chunk_index": 123,
"rotation": 0
}
]
},
"spreadsheet": {
"sheets": [
{
"cell_map_url": "<string>",
"sheet_name": "<string>"
}
]
},
"text": "<string>"
},
"file_name": "<string>",
"lane": "<string>",
"ocr_applied": true,
"units": 123,
"converted_pdf_url": "<string>",
"result_type": "parse"
},
"result_state": "available",
"source": {
"upload_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "upload"
},
"started_at": "2023-11-07T05:31:56Z",
"units": 0,
"workspace_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}{
"error": {
"code": "invalid_request",
"detail": null,
"message": "Request body has extra fields that this operation does not accept.",
"request_id": "req-01j9k2n3p4q5r6s7t8v9",
"retryable": false
}
}{
"error": {
"code": "invalid_request",
"detail": null,
"message": "Request body has extra fields that this operation does not accept.",
"request_id": "req-01j9k2n3p4q5r6s7t8v9",
"retryable": false
}
}deep_search
Run an agentic search over a workspace. Cost class: job.
The agentic layer over the context tools. The caller receives a 202
with a queued :class:~ndi_service.app.platform_api.results.Job
immediately, and polls GET /jobs/{id} to track progress and read the
:class:~ndi_service.app.platform_api.results.IntelligentSearchResult
once the job succeeds.
The agent picks its own retrieval tools, may search for minutes, and
authors the result’s evidences — per piece the page, the verbatim
quote, and its relevance, with every cited path resolved server-side
through the caller’s access gate into the uploaded file it belongs to
(source_path + source_file_id) — plus reasoning, its account
of how the pieces chain together. Every run answers: the result carries a
direct grounded answer and, beside it, interpretation — the
agent’s reading of the query (assumptions, resolved ambiguous terms, the
implied period or scope), stated even when the query was taken at face
value, so a misread query can be spotted and re-asked.
allow_clarification lets the agent reject a genuinely ambiguous query
instead of guessing: the succeeded result then carries the one question in
clarification (with answer null, evidences empty and
interpretation null). Answer it by re-asking in the same thread — send
the result’s session_id with the refined query. Off (the default) the
agent never asks back.
There is no effort knob: the route is the implementation choice, and the
agent loop always gets its full step budget — for the fixed single-shot
pipeline, call fact-search instead.
tier selects the main agent’s quality/cost class. Supporting model calls
inside retrieval tools retain their own defaults.
paths scopes the run to exactly those uploaded files: every retrieval
tool the agent runs — search, listings, reads, table queries, the shell —
sees only them. Each entry must name a visible uploaded source file
(file_not_found / access_denied otherwise, before any job is
created); it composes with path_prefix as an intersection.
A run keeps its conversation: pass the result’s session_id on a later
request to ask a follow-up in the same thread.
Retrieval only: this job never grounds. To pin a quote to a region of its
source document, call document-operations ground on the file the evidence
names (source_file_id) with its quote as the target text —
separately, and only for the citations you actually need a locator for.
The result also carries coverage plus a tool_calls trace with
argument digests rather than raw arguments. exhausted: true means
the step budget ran out before the agent finished — a successful response
(with an empty evidence list), not a failure.
curl --request POST \
--url https://ndi-api.nace.ai/v1/workspaces/{workspace_id}/deep-search \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"query": "<string>"
}
'{
"created_at": "2023-11-07T05:31:56Z",
"job_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"kind": "parse",
"status": "queued",
"deleted_at": "2023-11-07T05:31:56Z",
"effort": "<string>",
"error": {
"code": "invalid_request",
"message": "<string>",
"request_id": "<string>",
"retryable": true,
"detail": {}
},
"finished_at": "2023-11-07T05:31:56Z",
"force": true,
"idempotency_key": "<string>",
"name": "<string>",
"payload_expires_at": "2023-11-07T05:31:56Z",
"progress": {
"items_done": 0,
"items_failed": 0,
"items_total": 123,
"stage": "<string>",
"units_done": 0,
"units_total": 123
},
"project_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"query": "<string>",
"query_scope": {
"type": "workspace"
},
"result": {
"document": {
"page_count": 123,
"blocks": [
{
"content": "<string>",
"id": "<string>",
"type": "<string>",
"confidence": 123,
"image_url": "<string>",
"location": {
"page": 2,
"kind": "page_region",
"polygons": [
[
[
123,
123
]
]
]
}
}
],
"chunks": [
{
"content": "<string>",
"id": "<string>",
"location": {
"page": 2,
"kind": "page_region",
"polygons": [
[
[
123,
123
]
]
]
}
}
],
"markdown": "<string>",
"ocr": {
"lines": [
{
"bbox": {
"height": 123,
"left": 123,
"original_page": 123,
"page": 123,
"top": 123,
"width": 123
},
"confidence": 123,
"text": "<string>",
"chunk_index": 123,
"rotation": 0
}
],
"words": [
{
"bbox": {
"height": 123,
"left": 123,
"original_page": 123,
"page": 123,
"top": 123,
"width": 123
},
"confidence": 123,
"text": "<string>",
"chunk_index": 123,
"rotation": 0
}
]
},
"spreadsheet": {
"sheets": [
{
"cell_map_url": "<string>",
"sheet_name": "<string>"
}
]
},
"text": "<string>"
},
"file_name": "<string>",
"lane": "<string>",
"ocr_applied": true,
"units": 123,
"converted_pdf_url": "<string>",
"result_type": "parse"
},
"result_state": "available",
"source": {
"upload_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "upload"
},
"started_at": "2023-11-07T05:31:56Z",
"units": 0,
"workspace_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}{
"error": {
"code": "invalid_request",
"detail": null,
"message": "Request body has extra fields that this operation does not accept.",
"request_id": "req-01j9k2n3p4q5r6s7t8v9",
"retryable": false
}
}{
"error": {
"code": "invalid_request",
"detail": null,
"message": "Request body has extra fields that this operation does not accept.",
"request_id": "req-01j9k2n3p4q5r6s7t8v9",
"retryable": false
}
}Authorizations
Headers
Replaying a request with the same key returns the original job with 200, never a second job.
200Path Parameters
Workspace identifier.
Query Parameters
Bounded inline wait. 0 (the default) returns 202 immediately. Above 0 the route returns 200 with a terminal job if the work finished in time, and 202 with the still-running job if it did not. Either way, read status.
0 <= x <= 300Body
Body for POST /workspaces/{workspace_id}/deep-search — always the agent loop.
Deliberately no effort: the route IS the implementation choice, and the
agent loop always gets its full step budget. The strict base rejects the
removed field outright rather than silently accepting a knob that does
nothing. include_answer is removed the same way: every run answers now,
stating its reading of the query in the result's interpretation — the
evidence-only mode is gone, and the strict base rejects the field rather
than accepting a toggle with one legal value.
1 - 4096Let the agent finish the run with ONE clarifying question instead of an answer, when the query is genuinely ambiguous and the ambiguity materially changes the answer. The rejected result carries the question in clarification (with answer, interpretation and reasoning null and evidences empty); answer it by re-asking in the same thread via the result's session_id. Off — the default — means the agent always answers with its best reading, stated in interpretation.
20000Scope the run to exactly these uploaded files. Every retrieval tool the agent runs — search, listings, reads, table queries, the shell — sees only these files and the content ingested from them; everything else is invisible to the run. Each entry names an uploaded source file (never a folder — scope folders with path_prefix, which composes with this as an intersection). Unknown or inaccessible paths are refused up front.
1 - 64 elements1 - 1024Continue a prior deep-search conversation: the id echoed on an earlier result's session_id. The agent resumes with that thread's full context and treats this request's query as the follow-up. Omit to start a new thread. Only the API key that started the thread may continue it; a thread whose turn is still running returns 409 session_busy.
Quality and cost tier for the main deep-search agent. The service maps the tier to a catalogued model; supporting model calls keep their own defaults. The internal exact-model header overrides this selection when present.
low, medium, high Response
Successful Response
Every slow method returns one of these (spec 12 §1.4).
There is no /parse + /parse_async pair: every job-creating route
returns 202 with a queued job and accepts ?wait_seconds=, returning
200 with a terminal job if the work finished inside the window. The
caller's code path is the same either way — read status.
What a job is doing. Open enum — a new member is additive.
parse, split, classify, categorize, extract, ground, ingestion, reconciliation, kg_build, file_upload, file_replace, upload_session_complete, file_delete, workspace_delete, intelligent_search, qa_file, qa_tables, filtered_search, je_testing, deep_search_v2 cancelled is a first-class terminal status, not a failed variant.
Conflating them makes error-rate metrics lie and forces every client to string-match an error code.
queued, running, succeeded, failed, cancelled When the caller hid this run from listings, via delete_job. A soft delete: the job still reads by id and still counts toward GET /v1/usage — what it stops doing is appearing in GET /v1/jobs.
Which implementation ran this job, for the one kind whose request carries an effort (intelligent_search): 'fast' is fact-search, 'deep' (or the legacy 'balanced') is deep-search and the console's QA chat — a deep-search run under another surface, not a fourth implementation, so this field cannot tell those two apart. Null for every other kind. Read from the stored request, so it rides the same retention path as query and result: null once result_state leaves available.
Present only on failed.
Show child attributes
Show child attributes
The request's own force flag, echoed regardless of outcome. Present for kinds whose request carries one (e.g. kg_build, where a true value cleared the durable store before the job ran) so a caller can tell that apart from a build that failed leaving the prior store untouched — result alone cannot, since it is absent on failure either way.
Caller-chosen display name (the console's 'project'). Mutable via PATCH /v1/jobs/{job_id}.
Absent until running.
Show child attributes
Show child attributes
Caller-minted grouping id shared by jobs submitted together. Filterable on GET /v1/jobs.
The question this job was asked, for the search kinds that carry one (intelligent_search, qa_file, qa_tables, filtered_search); null for every other kind. Read from the stored request, so it rides the same retention path as result: null once result_state leaves available. Narrow by design — the frozen access context never reaches the wire. File scope is summarised separately as query_scope.
The question ran against the whole workspace.
- WorkspaceQueryScope
- FileQueryScope
- PathPrefixQueryScope
- FilesQueryScope
Show child attributes
Show child attributes
Present only on succeeded, and only while retained.
- ParseResult
- SplitResult
- ClassifyResult
- CategorizeResult
- ExtractResult
- GroundResult
- IngestionResult
- ReconciliationResult
- KnowledgeGraphBuildResult
- UploadFileResult
- ReplaceFileResult
- DeleteFileResult
- WorkspaceDeleteResult
- IntelligentSearchResult
- QaFileResult
- QaTablesResult
- FilteredSearchResult
- JetResult
- DeepSearchV2Result
Show child attributes
Show child attributes
Why Job.result is null.
available with a null result means the job has not succeeded yet;
expired means retention dropped it; not_retained means the client is
provisioned zero_retention and already had its one chance to read it.
A late poll returns the job with a state, never a 404 — "this expired" and
"this never existed" are a scheduling problem and a bug respectively, and a
404 makes them identical.
available, expired, not_retained Bytes staged through POST /v1/uploads. Single-use, TTL-bound.
- UploadSource
- UrlSource
- WorkspaceFileSource
- ParseResultSource
Show child attributes
Show child attributes
The workspace this job reads. Null for a document operation whose source is an upload, a URL, or a prior parse result.