> ## 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.

# KG tools

> Query the knowledge graph — kg-info, kg-search, kg-walk

Three tools for querying the knowledge graph built by
[`POST .../knowledge-graph/builds`](/api-reference/knowledge-graph).
All return `coverage` reporting files withheld by the access predicate.
All return `409 kg_not_built` when no graph exists for the workspace.

***

## KG info

```
GET /v1/workspaces/{workspace_id}/tools/kg-info
```

Snapshot metadata for the workspace knowledge graph. Call this first to confirm the
graph exists and to discover hub node IDs for `kg-walk`.

**Cost class: cacheable.**

**Query parameters:**

* **`path_prefix`** (optional) — Scope node/edge counts to files under this prefix.
* **`include_graph`** (optional, default `false`) — Also return a drawable sample subgraph.
* **`graph_nodes`** (optional, default 150, max 500) — Node budget for the sample. Ignored when `include_graph=false`.

```bash theme={"dark"}
curl "$NDI_BASE_URL/v1/workspaces/$WS/tools/kg-info?include_graph=false" \
  -H "X-API-Key: $NDI_API_KEY"
```

**Response:**

```json theme={"dark"}
{
  "snapshot_id": "snap-abc123",
  "built_at": "2026-08-09T12:30:00Z",
  "node_types": [
    {"type": "Organization", "count": 142},
    {"type": "Person", "count": 88}
  ],
  "edge_types": [
    {"type": "subsidiary_of", "count": 47}
  ],
  "node_total": 1247,
  "edge_total": 3156,
  "hubs": [
    {"node_id": "node-42", "node_type": "Organization", "title": "Acme Corp", "degree": 210}
  ],
  "path_prefix": null,
  "coverage": {"restricted_candidates": 0, "labels_required": []},
  "graph": null
}
```

**Response fields:**

* **`snapshot_id`** — Changes when the graph is rebuilt; use to detect staleness.
* **`node_types`** / **`edge_types`** — Counts by type.
* **`hubs`** — Highest-degree nodes in the caller-visible slice (good `kg-walk` starting points).
* **`graph`** — Drawable subgraph sample when `include_graph=true`; `null` otherwise.

***

## KG search

```
POST /v1/workspaces/{workspace_id}/tools/kg-search
```

Find graph nodes by semantic similarity to a query. Returns nodes with their facts
and source-file provenance.

**Cost class: medium.**

**Request body:**

```json theme={"dark"}
{
  "query": "subsidiaries of Acme Corp",
  "k": 10,
  "node_types": ["Organization"],
  "path_prefix": null
}
```

**Body fields:**

* **`query`** (required, max 4096 chars) — Natural-language or keyword search.
* **`k`** (optional, default 10, max 100) — Number of results.
* **`node_types`** (optional) — Restrict to a subset of node types from `kg-info`.
* **`path_prefix`** (optional) — Restrict to nodes sourced from files under this prefix.

```bash theme={"dark"}
curl -X POST "$NDI_BASE_URL/v1/workspaces/$WS/tools/kg-search" \
  -H "X-API-Key: $NDI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "subsidiaries of Acme Corp", "k": 5}'
```

**Response:**

```json theme={"dark"}
{
  "nodes": [
    {
      "node_id": "node-43",
      "node_type": "Organization",
      "title": "Subsidiary Y",
      "facts": [
        {
          "predicate": "incorporated_in",
          "value": "Delaware",
          "evidence": {"file_id": "...", "path": "report.pdf", "locator": {...}, "snippet": "...", "relevance_score": 0.95, "why": "...", "component": "certificate_of_incorporation"}
        }
      ],
      "source_files": [{"file_id": "...", "path": "report.pdf"}],
      "degree": 12
    }
  ],
  "coverage": {"restricted_candidates": 0, "labels_required": []}
}
```

Each fact's `evidence.component` names the public component of the source file the
fact was extracted from — a spreadsheet tab name or a split document's section —
exactly what [`qa-file` / `read-file`](/api-reference/file-tools) accept as
`component=`, so a fact hands off directly to a targeted read. For document
sections the locator's `page` is a real source page and is kept alongside
`component`; for spreadsheet facts the locator carries **no page** (tabs have no
pages — address the tab by `component`), and the tab name is best-effort — a
fact from a small tab processed together with its neighbors can name an
adjacent tab of the same file. `component` is `null` for single-component
files or when attribution is unavailable.

***

## KG walk

```
POST /v1/workspaces/{workspace_id}/tools/kg-walk
```

Traverse the graph from a set of seed nodes, following relationships up to a hop
limit. Returns nodes, edges, and the paths that explain why each node was reached.

**Cost class: medium.**

**Request body:**

```json theme={"dark"}
{
  "start_node_ids": ["node-42"],
  "hops": 2,
  "edge_types": null,
  "node_types": null,
  "max_nodes": 100,
  "path_prefix": null
}
```

**Body fields:**

* **`start_node_ids`** (required, 1–50) — Node IDs from `kg-info` hubs or `kg-search` results.
* **`hops`** (optional, default 2, max 5) — Breadth-first hop budget.
* **`edge_types`** (optional) — Restrict traversal to these edge types.
* **`node_types`** (optional) — Include only these node types in the result.
* **`max_nodes`** (optional, default 100, max 500) — Maximum nodes to return.
* **`path_prefix`** (optional) — Restrict to nodes sourced from files under this prefix. A walk cannot traverse out of scope and back in.

```bash theme={"dark"}
curl -X POST "$NDI_BASE_URL/v1/workspaces/$WS/tools/kg-walk" \
  -H "X-API-Key: $NDI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"start_node_ids": ["node-42"], "hops": 2}'
```

**Response:**

```json theme={"dark"}
{
  "nodes": [
    {"node_id": "node-42", "node_type": "Organization", "title": "Acme Corp", "facts": [...], "source_files": [...], "degree": 210},
    {"node_id": "node-43", "node_type": "Organization", "title": "Subsidiary Y", "facts": [...], "source_files": [...], "degree": 12}
  ],
  "edges": [
    {"edge_id": "edge-1", "edge_type": "subsidiary_of", "from_node_id": "node-43", "to_node_id": "node-42", "evidence": {...}}
  ],
  "paths": [
    {"node_ids": ["node-42", "node-43"], "edge_ids": ["edge-1"]}
  ],
  "truncated": false,
  "coverage": {"restricted_candidates": 0, "labels_required": []}
}
```

**Response fields:**

* **`nodes`** — All reached nodes with facts and provenance.
* **`edges`** — Edges traversed, with optional evidence. Edge evidence carries the same `component` handoff as `kg-search` facts: pass it to `qa-file` / `read-file` as `component=` (spreadsheet-tab evidence has `component` and no page; document evidence keeps its real page).
* **`paths`** — The hop sequences justifying each node's inclusion.
* **`truncated`** — `true` when `max_nodes` was hit before the walk was complete.

***

## Typical workflow

```bash theme={"dark"}
# 1. Confirm the graph exists and get hub node IDs
curl "$NDI_BASE_URL/v1/workspaces/$WS/tools/kg-info" -H "X-API-Key: $NDI_API_KEY"

# 2. Find entities by query
curl -X POST "$NDI_BASE_URL/v1/workspaces/$WS/tools/kg-search" \
  -H "X-API-Key: $NDI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "revenue recognition policy", "k": 5}'

# 3. Walk from discovered nodes
curl -X POST "$NDI_BASE_URL/v1/workspaces/$WS/tools/kg-walk" \
  -H "X-API-Key: $NDI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"start_node_ids": ["node-42"], "hops": 2}'
```
