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

# validate()

> Cross-check a dataset's graph and vector stores for integrity problems

# cognee.validate()

```python theme={null}
async def validate(
    dataset: Optional[Union[str, List[str]]] = "main_dataset",
    user: Optional[User] = None,
) -> ValidationReport
```

## Description

Cross-check the graph and vector stores of a dataset and report where they disagree.
`validate()` answers three questions no other call answers: does every edge still point
at nodes that exist, does every deduplicated node still carry the id its own dedup
contract derives, and is every embeddable node actually present in its vector collection.

The check is **read-only** — it never writes to any store, so it is safe to run against a
production dataset. It is also **backend-agnostic**: it is driven entirely through
`GraphDBInterface.get_graph_data()` and `VectorDBInterface.retrieve()`, so it works
unmodified against every supported graph and vector backend without adapter-specific
code.

Run it after [`cognify()`](/python-api/cognify), after a large import, or after a
migration — the moments when the graph and the vector index can drift apart.

## Parameters

<ParamField path="dataset" type="Optional[Union[str, List[str]]]" default="'main_dataset'">Dataset name(s) to validate. A single string is treated as a one-element list.</ParamField>
<ParamField path="user" type="Optional[User]" default="None">User context for dataset access. Falls back to the default user.</ParamField>

<Note>
  Only **one** graph is checked per call: `dataset` is resolved to the datasets you have
  `read` permission on, and the **first** of those determines which graph and vector store
  are read. Passing several dataset names does not merge them into a combined report — call
  `validate()` once per dataset instead.
</Note>

<Warning>
  If none of the given names resolves to a dataset you can read, no dataset context is
  entered. With backend access control enabled (`ENABLE_BACKEND_ACCESS_CONTROL=True`, the
  default) that raises — a dataset is required to resolve the per-dataset databases. With
  access control disabled, the call falls back to the single shared graph and vector stores
  instead, so a typo in a dataset name yields a report about the default stores rather than
  an error. Check `summary["graph_nodes"]` before reading a `healthy` status as proof that a
  specific dataset is intact.
</Warning>

## Returns

`ValidationReport` — a Pydantic model with three fields:

| Field     | Type                    | Contents                                                                                                                                                                      |
| --------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `status`  | `ValidationStatus`      | `"healthy"`, `"degraded"`, or `"unhealthy"`.                                                                                                                                  |
| `summary` | `Dict[str, Any]`        | `graph_nodes` (int), `graph_edges` (int), and `node_type_distribution` — a `{node type: count}` map over the whole graph, where typeless nodes are counted under `"unknown"`. |
| `issues`  | `List[ValidationIssue]` | Every issue found, each with `severity` (`"error"` / `"warning"`), `type`, and a human-readable `detail` naming the node or edge.                                             |

`status` is derived from the severities present, not from issue counts:

* any `error`-severity issue → **`unhealthy`**
* otherwise, any `warning`-severity issue → **`degraded`**
* no issues at all → **`healthy`**

`validate`, `ValidationReport`, `ValidationIssue`, and `ValidationStatus` are importable
from the `cognee` top level; the `IssueSeverity` and `IssueType` enums are available from
`cognee.api.v1.validate`. All three enums (`ValidationStatus`, `IssueSeverity`,
`IssueType`) subclass `str`, so comparing against plain strings
(`report.status == "healthy"`) works without importing them — but printing a member
shows the enum repr (`ValidationStatus.HEALTHY`), so use `.value` when you want the
plain string.

## What is checked

<AccordionGroup>
  <Accordion title="orphaned_edge — error">
    An edge whose `source` or `target` id is not in the graph's node set. Traversal that
    reaches such an edge hits a dead end, so graph-based search silently loses the
    connection.

    The `detail` names the edge as `source -[relationship]-> target` along with the id(s)
    that are missing. Typically the result of nodes being removed without their edges;
    re-running `cognify()` for the affected dataset, or deleting and re-ingesting the
    source data, rebuilds a consistent edge set.
  </Accordion>

  <Accordion title="identity_id_mismatch — warning">
    A node of a type that declares `identity_fields` — `Entity` and `EntityType`, both
    keyed on `name` — whose id does not equal the id its own class derives from its
    properties via [`Type.id_for(...)`](/core-concepts/building-blocks/datapoints).

    Cognee's dedup contract is that two nodes with the same identity value *are* the same
    node because they hash to the same id. A node that reached the graph without going
    through that contract (a raw write from an importer or a migration, or a node written
    by an older Cognee version) can carry a stale or arbitrary id — which means a second,
    correctly-derived node for the same entity can coexist as an undetected duplicate.
    Hence a warning rather than an error: nothing is broken yet, but deduplication is no
    longer guaranteed for that node.

    The check is skipped for a node whose identity field is absent from its graph
    properties — the id cannot be recomputed, and the node may simply predate the field.
    To bring mismatched nodes onto the current scheme, re-cognify the affected datasets
    from scratch so their ids are derived by the models.
  </Accordion>

  <Accordion title="missing_vector_entry — error">
    A node of a type that declares `index_fields` — `Entity` (collection `Entity_name`)
    and `DocumentChunk` (collection `DocumentChunk_text`) — with no matching point in its
    `{type}_{index_field}` vector collection.

    The node exists in the graph but is unreachable by every embedding-based search type,
    silently: semantic search cannot surface it and cannot use it to seed graph traversal.
    Re-running `cognify()` for the dataset re-indexes the missing nodes. If an entire node
    type reports one issue per node, the collection itself is likely missing or empty
    rather than individual points having been lost.
  </Accordion>
</AccordionGroup>

The checked types are read from the real model classes' `identity_fields` / `index_fields`
metadata rather than being hardcoded, so a change to either contract is picked up here
automatically.

## Cost and when to run

* **No LLM calls** and no writes. The cost is entirely database reads.
* The graph is read in full through `get_graph_data()`, so time and memory scale with the
  total number of nodes and edges — not with a sample. There is no sampling or limit
  parameter.
* Vector lookups are **one batched `retrieve()` per collection** (at most two: `Entity_name`
  and `DocumentChunk_text`), not one call per node.
* Because it is read-only, it is safe against production stores — but on a very large
  dataset prefer a low-load window, and note that the number of `issues` is unbounded: a
  badly drifted graph can return one issue per affected node or edge.

Good moments to run it: after `cognify()` or a bulk import, after applying migrations, as
a pre-flight check in CI, or on a schedule as integrity monitoring.

## Examples

```python theme={null}
import cognee

await cognee.add("docs/handbook.pdf", dataset_name="onboarding")
await cognee.cognify(datasets=["onboarding"])

report = await cognee.validate(dataset="onboarding")

print(report.status.value)                # "healthy" | "degraded" | "unhealthy"
print(report.summary["graph_nodes"], report.summary["graph_edges"])
print(report.summary["node_type_distribution"])  # {"Entity": 128, "DocumentChunk": 14, ...}

for issue in report.issues:
    print(f"[{issue.severity.value}] {issue.type.value}: {issue.detail}")
```

Acting on the status — for example, failing a CI pre-flight check on errors while letting
warnings through:

```python theme={null}
import cognee
from cognee import ValidationStatus

report = await cognee.validate(dataset="onboarding")

if report.status == ValidationStatus.UNHEALTHY:
    errors = [i for i in report.issues if i.severity == "error"]
    raise SystemExit(f"dataset integrity check failed with {len(errors)} error(s)")

if report.status == ValidationStatus.DEGRADED:
    print(f"{len(report.issues)} warning(s) — deduplication may be incomplete")
```

Grouping issues by type to decide what to remediate:

```python theme={null}
from collections import Counter

report = await cognee.validate(dataset="onboarding")
print(Counter(issue.type.value for issue in report.issues))
# Counter({'missing_vector_entry': 12, 'identity_id_mismatch': 3})
```

## Over HTTP

The same check is exposed as a read-only endpoint on the API server:

```
GET /api/v1/validate
```

| Aspect                      | Behavior                                                                                                                                                  |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `dataset` query parameter   | Repeatable; defaults to `main_dataset`. Pass `?dataset=a&dataset=b` to hand several names to the same resolution rule (the first authorized one is read). |
| Authentication              | Required — the endpoint resolves the calling user and applies the same `read` permission check as the SDK function.                                       |
| `200 OK`                    | Report body, when `status` is `healthy` or `degraded`.                                                                                                    |
| `503 Service Unavailable`   | Report body, when `status` is `unhealthy` — that is, whenever at least one `error`-severity issue was found.                                              |
| `500 Internal Server Error` | `{"status": "error", "reason": "validation failed: ..."}` if the check itself raised.                                                                     |

<Warning>
  The `503` is the single detail to plan for when wiring this into a health probe or a CI
  check: an **`unhealthy` report is returned with a `503` status code**, not a `200`. Clients
  that raise on non-2xx responses will treat a successful-but-failing validation as a
  transport error, and a load balancer pointed at this path will pull the instance out of
  rotation on a data-integrity finding. Read the response body to tell the two apart — a
  `503` from this endpoint still carries the full report JSON, while a `500` carries
  `{"status": "error", "reason": ...}`.
</Warning>

```bash theme={null}
curl -i "$COGNEE_URL/api/v1/validate?dataset=onboarding" \
  -H "Authorization: Bearer $COGNEE_TOKEN"
```

```json theme={null}
{
  "status": "unhealthy",
  "summary": {
    "graph_nodes": 142,
    "graph_edges": 310,
    "node_type_distribution": { "Entity": 128, "DocumentChunk": 14 }
  },
  "issues": [
    {
      "severity": "error",
      "type": "missing_vector_entry",
      "detail": "Entity node '...' has no matching point in vector collection 'Entity_name' — it exists in the graph but is unreachable by semantic search."
    }
  ]
}
```

## See also

* [`report()`](/python-api/report) — the other read-only diagnostic: what the graph *contains*, rather than whether it is consistent
* [`cognify()`](/python-api/cognify) — the pipeline that writes the nodes, edges, and vector points this call cross-checks
* [DataPoints](/core-concepts/building-blocks/datapoints) — `identity_fields`, `index_fields`, and the `id_for()` contract the identity check verifies
* [`run_startup_migrations()`](/python-api/run-migrations) — apply pending schema migrations; validating afterwards confirms the stores still agree
