> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ndi.nace.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Ground

> Locate values inside a source document with precise coordinates

Ground answers "*where* does this value appear in the source document?" You submit one or more items — literal strings or semantic descriptions — and get back, per item, the locations where it was found: normalized bounding boxes on pages, cell ranges in spreadsheets, character ranges in text files, or line indices in JSONL.

Typical uses: drawing citation highlights over a PDF viewer, verifying that an extracted figure really exists in the source, and linking derived data back to its provenance.

| Endpoint             | Behavior                                                                |
| -------------------- | ----------------------------------------------------------------------- |
| `POST /ground`       | Starts the job and waits for the result (bounded by `timeout_seconds`). |
| `POST /ground_async` | Starts the job and returns `202 {"job_id": …}` immediately.             |

## Request

<ParamField query="timeout_seconds" type="number" default="120">
  **Sync endpoint only.** Max seconds to wait, between 1 and 300.
</ParamField>

<ParamField body="file" type="object" required>
  The document to search. Same shape as everywhere else — `source_url`
  (+ `file_name` for `s3://`/`https://`). See [Files & sources](/concepts/files).
</ParamField>

<ParamField body="items" type="array" required>
  Values to locate — between 1 and 30 per request. Each item is matched
  independently; split larger batches across requests.

  <Expandable title="item properties">
    <ParamField body="item_id" type="string" required>
      Your identifier for this item, echoed back in the result so you can
      correlate.
    </ParamField>

    <ParamField body="query" type="string" required>
      The text, value, or phrase to locate. Closest match wins.
    </ParamField>

    <ParamField body="semantic" type="boolean" default="false">
      When `true`, `query` is a *description* of what to find (e.g.
      `"total liabilities"` should match the line item even if the document
      words it differently). When `false`, matching is literal.
    </ParamField>

    <ParamField body="hints" type="object">
      Optional hints that narrow the search and improve precision.

      <Expandable title="hints properties">
        <ParamField body="visual.anchor_text" type="string">
          Text expected near the target — disambiguates repeated values.
        </ParamField>

        <ParamField body="visual.page_hints" type="integer[]">
          1-based page numbers to prioritize.
        </ParamField>

        <ParamField body="spreadsheet.sheet_name" type="string">
          Sheet expected to contain the value.
        </ParamField>

        <ParamField body="spreadsheet.cell_hint" type="string">
          Cell hint, e.g. `"B12"`.
        </ParamField>

        <ParamField body="spreadsheet.range_hint" type="string">
          Range hint, e.g. `"B12:D18"`.
        </ParamField>
      </Expandable>
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="options" type="object">
  <Expandable title="options properties">
    <ParamField body="confidence_threshold" type="number" default="0.75">
      Minimum confidence (0–1) for a match to be included.
    </ParamField>

    <ParamField body="return_cropped_images" type="boolean" default="false">
      When `true`, visual matches also include a `cropped_image_url` — an
      image of the matched region, useful for human review UIs.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="derived" type="object">
  Artifacts from prior NDI runs on the **same file** that speed up and improve
  grounding. All URLs must be `s3://` URLs previously returned by NDI.

  <Expandable title="derived properties">
    <ParamField body="markdown_url" type="string">
      `markdown_url` from a prior parse of this file.
    </ParamField>

    <ParamField body="transcript_url" type="string">
      Transcript derived from the source media.
    </ParamField>

    <ParamField body="jsonl_url" type="string">
      JSONL artifact derived from the source.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="idempotency_key" type="string">
  Up to 128 characters. See [Idempotency](/concepts/jobs#idempotency).
</ParamField>

Supported extensions: everything parse supports **except** `.eml` `.msg` `.vsd` `.vsdx`, **plus** `.bmp` `.jsonl` `.md` `.mdx`.

## Result

<ResponseField name="result_type" type="&#x22;ground&#x22;">
  Discriminator — always `"ground"`.
</ResponseField>

<ResponseField name="results" type="array">
  One entry per requested item, in no guaranteed order — correlate by
  `item_id`.

  <Expandable title="per-item result">
    <ResponseField name="item_id" type="string">Echo of your identifier.</ResponseField>
    <ResponseField name="found" type="boolean">Whether any match cleared the confidence threshold.</ResponseField>

    <ResponseField name="matches" type="array">
      Locations, tagged by `match_type` (see match types below).
    </ResponseField>

    <ResponseField name="error" type="string | null">Per-item failure detail.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="total_items_found" type="integer">Items with at least one match.</ResponseField>
<ResponseField name="total_items_requested" type="integer">Items submitted.</ResponseField>
<ResponseField name="duration_ms" type="integer">Processing time.</ResponseField>

### Match types

Every match carries `confidence` (0–1) and usually `matched_text` (the text found at the location). The location fields depend on `match_type`:

| `match_type`        | Location fields                                                                                                                         | Produced for                     |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- |
| `visual_region`     | `page` (1-indexed), `x`, `y`, `w`, `h` — a bounding box **normalized to the page (0–1)**, origin top-left; optional `cropped_image_url` | PDFs, images, rendered documents |
| `spreadsheet_range` | `sheet_name`, `cell_range` (e.g. `"B12:D14"`)                                                                                           | Spreadsheets                     |
| `text_range`        | `start_char`, `end_char` — offsets into the text                                                                                        | Plain-text sources               |
| `jsonl_record`      | `line_index`, `keys`                                                                                                                    | JSONL sources                    |
| `audio_range`       | `start_ms`, `end_ms`                                                                                                                    | Media transcripts                |

<Tip>
  To draw a highlight from a `visual_region` at render size `W×H` pixels:
  `left = x*W`, `top = y*H`, `width = w*W`, `height = h*H`.
</Tip>

## Examples

<CodeGroup>
  ```bash Semantic + hints theme={"dark"}
  curl -s "$NDI_BASE/ground" \
    -H "X-API-Key: $NDI_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "file": { "source_url": "ndi://file/7f4d2a10-3b7e-4c25-9f61-8f0f4f0a1b2c" },
      "items": [
        {
          "item_id": "liab-total",
          "query": "total liabilities",
          "semantic": true,
          "hints": { "visual": { "anchor_text": "Balance Sheet", "page_hints": [41, 42, 43] } }
        },
        {
          "item_id": "auditor",
          "query": "Independent Auditor'\''s Report",
          "semantic": false
        }
      ],
      "options": { "confidence_threshold": 0.8, "return_cropped_images": true },
      "idempotency_key": "ground-annual-report-v1"
    }'
  ```

  ```bash Spreadsheet theme={"dark"}
  curl -s "$NDI_BASE/ground" \
    -H "X-API-Key: $NDI_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "file": {
        "source_url": "https://example.com/trial-balance.xlsx",
        "file_name": "trial-balance.xlsx"
      },
      "items": [
        {
          "item_id": "cash-eoy",
          "query": "1,204,331.00",
          "hints": { "spreadsheet": { "sheet_name": "TB Dec 2025" } }
        }
      ]
    }'
  ```
</CodeGroup>

```json Result theme={"dark"}
{
  "job_id": "b1e6d5c4-2a3f-4e89-a0b1-c2d3e4f5a6b7",
  "action": "ground",
  "status": "succeeded",
  "result": {
    "result_type": "ground",
    "results": [
      {
        "item_id": "liab-total",
        "found": true,
        "matches": [
          {
            "match_type": "visual_region",
            "page": 42,
            "x": 0.118, "y": 0.552, "w": 0.312, "h": 0.021,
            "confidence": 0.93,
            "matched_text": "Total liabilities  $1,204,331",
            "cropped_image_url": "https://…(time-limited)…/crop-0.png"
          }
        ],
        "error": null
      },
      {
        "item_id": "auditor",
        "found": false,
        "matches": [],
        "error": null
      }
    ],
    "total_items_found": 1,
    "total_items_requested": 2,
    "duration_ms": 8412
  },
  "error": null,
  "created_at": "2026-07-12T21:15:40Z",
  "started_at": "2026-07-12T21:15:41Z",
  "completed_at": "2026-07-12T21:15:49Z"
}
```
