Skip to main content
Fact search finds localized evidence; deep search explores files agentically. Filtered search answers the third kind of question — corpus-wide filter/rank/count requests such as “all invoices over $500”, “top five invoices by spend”, or “how many unpaid invoices reference a PO” — from a structured metadata catalog built at ingestion: typed, evidence-backed fields per logical component (a document section, a spreadsheet tab, a recording) of every ingested file. File content is read only for residual semantic conditions (“…that mention a copper purchase”), and only over catalog-selected candidates. The result is a table: typed columns, matching components as rows (each named by its uploaded file), an exact total_matches, and a citation per extracted value.
One endpoint, a discriminated body. {"type": "query", ...} starts a search; {"type": "page", ...} fetches the next page of an earlier one. Query parameters:
  • wait_seconds (optional, default 0, max 300) — initial queries run an agent and usually outlast a sync window; page requests typically finish inside it.
Headers:
  • Idempotency-Key (optional) — replays match on the full canonical body and the calling API key.
Request body:
  • query — the filter/rank/count request, in plain words.
  • context (optional) — free-form background for the interpretation; it does not alter the access filter.
  • path_prefix (optional) — narrow to a folder subtree. Intersects with the access filter, never overrides it.
  • allow_clarification (optional, default false) — let the run finish with one clarifying question instead of a result, when the request is genuinely ambiguous and the ambiguity materially changes the result. The rejected result carries the question in clarification — no rows, no applied_query, exhaustive false. Filtered search keeps no session, so answer it by sending a new initial query with the ambiguity resolved. Off — the default — the run always executes, stating its reading in interpretation.
Returns 202 with a job of kind: "filtered_search"; poll GET /v1/jobs/{id} for the FilteredSearchResult. Result (abridged):
Result shapes follow the question: a count question returns the exact total_matches with no rows; a top-N question returns exactly N rows with the pre-cutoff total; a list returns a bounded first page (≤ 200 rows) plus next_cursor — matching more than one page never requires narrowing the query.

Page a result

Request body:
Pages are live: each page re-executes the original query against the current catalog under your current access — no agent runs, so pages are fast. Concurrent ingestion, deletion, or relabeling may shift later pages (rows can repeat or be skipped across page boundaries); every result carries its own executed_at, and catalog_drifted: true says the catalog changed since the query was validated. Only the API key that ran the original query may page it; an expired or purged parent job returns invalid_request — re-run the query.

Reading the result honestly

  • exhaustive is the one completeness signal. true means every eligible component in the tables the query touched was completely cataloged and the whole qualifying set was evaluated — totals are a census and a zero-match result proves absence. When false, the matches shown are valid, but “all”-style claims, total_matches as a census, and zero-match conclusions are not guaranteed (uncataloged files in scope, degraded extractions, or a residual content condition — which can prove presence, never absence).
  • interpretation is the agent’s reading of your request — thresholds, field choices, assumptions. A misreading is visible there; re-ask with tighter wording.
  • clarification is non-null exactly when the run asked a clarifying question instead of executing — only possible when the initial query set allow_clarification. A rejected result carries no rows, no applied_query, no interpretation, and exhaustive: false. There is no session to continue: send a new initial query that answers the question.
  • Money is never converted. Every amount carries the ISO currency the source stated, and cross-currency comparison or aggregation is unsupported in this version: when amounts are compared or ranked, the interpretation states the common-currency assumption.
  • Value statuses are data honesty: a cell’s status of not_found means the component was examined and the field is absent (usable as a filter — “invoices with no PO”); ambiguous means present but not normalizable (e.g. money without a currency); unexamined means the extraction could not say.
  • coverage is the standard access-withholding block: results are label-filtered first, and the narrowing is reported, never silent.
  • component is actionable. A row’s component is the same name the file tools take: pass it as component= to qa-file / read-file (or in query-tablescomponents for a sheet row) to work on exactly that part of the file. Several document rows can share one section name — page_start / page_end tell them apart. The one exception is a media row: recordings are unnamed in the file tools, so its component (the file stem) is display-only and QA takes the whole file.

Coverage and re-ingestion

The catalog is built at ingestion. Files ingested before filtered search existed remain valid but uncovered — they block exhaustive for every query over their scope until they are re-ingested (POST /v1/workspaces/{id}/ingestions over the same selection re-ingests in place).