> This page is for version v4 (default).
> For other versions, use one of these documentation indexes:
> - v4 (default): https://help.getzep.com/v4/llms.txt
> - v3: https://help.getzep.com/v3/llms.txt
> - v2: https://help.getzep.com/v2/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://help.getzep.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://help.getzep.com/_mcp/server.

# Reading Data from the Graph

Zep provides APIs to read Edges, Nodes, and Episodes from the graph. You can read one item by its `uuid`, or list the items of one type in one graph. Each call takes the `graph_uuid` of the graph. For a user graph, use the UUID of the user. For a shared Context Graph, use the `uuid` that the graph create response gave you.

The dashboard graph visualizer includes a [graph visualizer assistant](/graph-visualizer-assistant) that answers questions about the graph you are viewing.

Examples of each retrieval method are provided below.

## Reading Edges

For one fact that relates more than two nodes, read the [hyperedge](/hyperedges) instead of its member edges.

Alongside `source_node_uuid` and `target_node_uuid`, edge responses can include `source_node_name`, `target_node_name`, `source_node_labels`, and `target_node_labels`. These are projections of current node state, so a node rename shows up on the next read. Zep omits the corresponding fields when an endpoint node cannot be resolved, and omits all four when the API key has active attribute constraints. The edge is still returned with its endpoint UUIDs.

**`Python`**

```python Python
from zep_cloud.client import Zep

client = Zep(
    api_key=API_KEY,
)

edge = client.graph.edge.get(graph_uuid, edge_uuid)
```

**`TypeScript`**

```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";

const client = new ZepClient({
  apiKey: API_KEY,
});

const edge = await client.graph.edge.get(graphUuid, edgeUuid);
```

**`Go`**

```go Go
import (
    "context"
    "fmt"
    "log"

    zepclient "github.com/getzep/zep-go/v4/client"
    "github.com/getzep/zep-go/v4/option"
)

ctx := context.TODO()

client := zepclient.NewClient(option.WithAPIKey(apiKey))

edge, err := client.Graph.Edge.Get(ctx, graphUUID, edgeUUID)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Edge: %+v\n", edge)
```

## Reading nodes

**`Python`**

```python Python
from zep_cloud.client import Zep

client = Zep(
    api_key=API_KEY,
)

node = client.graph.node.get(graph_uuid, node_uuid)
```

**`TypeScript`**

```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";

const client = new ZepClient({
  apiKey: API_KEY,
});

const node = await client.graph.node.get(graphUuid, nodeUuid);
```

**`Go`**

```go Go
import (
    "context"
    "fmt"
    "log"

    zepclient "github.com/getzep/zep-go/v4/client"
    "github.com/getzep/zep-go/v4/option"
)

ctx := context.TODO()

client := zepclient.NewClient(option.WithAPIKey(apiKey))

node, err := client.Graph.Node.Get(ctx, graphUUID, nodeUUID)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Node: %+v\n", node)
```

## Reading Episodes

**`Python`**

```python Python
from zep_cloud.client import Zep

client = Zep(
    api_key=API_KEY,
)

episode = client.graph.episode.get(graph_uuid, episode_uuid)
```

**`TypeScript`**

```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";

const client = new ZepClient({
  apiKey: API_KEY,
});

const episode = await client.graph.episode.get(graphUuid, episodeUuid);
```

**`Go`**

```go Go
import (
    "context"
    "fmt"
    "log"

    zepclient "github.com/getzep/zep-go/v4/client"
    "github.com/getzep/zep-go/v4/option"
)

ctx := context.TODO()

client := zepclient.NewClient(option.WithAPIKey(apiKey))

episode, err := client.Graph.Episode.Get(ctx, graphUUID, episodeUUID)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Episode: %+v\n", episode)
```

## Listing artifacts in bulk

The methods above return a single artifact by its `uuid`. To enumerate artifacts of a given type in bulk, use the list methods. Where [searching the graph](/searching-the-graph) ranks results by relevance to a query, the list methods return everything of a given type in a graph, with filtering, sorting, and pagination applied server-side.

Reach for these methods when you are not answering a question but enumerating data: rendering an entity browser or a facts table in a UI, exporting a graph, or auditing what a graph contains. Cursor pagination lets you walk large graphs in stable, predictable pages instead of pulling everything into memory at once.

Each artifact type has one list method. The method takes the `graph_uuid` and works the same way on a user graph and on a shared Context Graph:

| Artifact           | Method                        | Detail                                |
| ------------------ | ----------------------------- | ------------------------------------- |
| Nodes (entities)   | `graph.node.list`             | [Entities](/entities)                 |
| Edges (facts)      | `graph.edge.list`             | [Facts](/facts)                       |
| Episodes           | `graph.episode.list`          | [Listing episodes](#listing-episodes) |
| Observations       | `graph.observation.list`      | [Observations](/observations)         |
| Thread summaries   | `graph.thread_summary.list`   | [Thread summaries](/thread-summaries) |
| Document summaries | `graph.document_summary.list` | [Documents](/documents)               |

### Shared parameters

| Parameter   | Type    | Description                                                                                                                              | Default |
| ----------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `filters`   | object  | Restrict which artifacts are returned. Zep sends it in the request body and applies it on the server. See [List filters](#list-filters). | –       |
| `page_size` | integer | Maximum number of items per page, 1–100.                                                                                                 | `50`    |
| `cursor`    | string  | Opaque cursor for the next page. The SDK pager sends it for you (see [Pagination](#pagination)).                                         | –       |

`graph.node.list` also takes `order_by` (`"uuid"` or `"degree"`) and `order` (`"asc"` or `"desc"`). The default is `uuid`, descending.

### List filters

The `filters` object is typed in all three SDKs. The node, edge, observation, thread summary, and document summary lists take `SearchFilters`. `graph.episode.list` takes `EpisodeListFilters`. In Python, you can also pass a dictionary with snake case keys. In TypeScript, write the keys in camel case, for example `connectedNodeUuids`. The TypeScript SDK removes the keys that it does not know, thus a snake case key has no effect. In Go, use the `zep.SearchFilters` and `zep.EpisodeListFilters` structs. The server checks each key, and combines each key with the scope of the list with a logical AND. A key that the list cannot apply gets HTTP 400 with the code `unsupported_filter`.

The keys use the same semantics as the [Search Filters](/searching-the-graph#search-filters) of graph search: `date_filters` (`any_of` groups of `all_of` leaf predicates on `created_at`, `valid_at`, `invalid_at`, or `expired_at`), `edge_types` / `exclude_edge_types`, `node_labels` / `exclude_node_labels`, `property_filters`, and `metadata_filters`. These keys apply to some lists only:

* `connected_node_uuids`, `source_node_uuids`, and `target_node_uuids` apply to `graph.edge.list` only.
* `episode_uuids` selects the nodes or edges that the listed episodes mention. The observation, thread summary, and document summary lists do not accept it.
* `graph.episode.list` accepts only `mentioned_node_uuids`, `metadata_filters`, and `content_policy_violated`. `content_policy_violated` is a boolean. Zep accepts it only when content policies are available and the project setting `include_policy_violating_episodes` is `true`. Otherwise, the request gets HTTP 400 with the code `unsupported_filter`. See [Filter by violation](/content-policies#filter-by-violation).

### Listing nodes

List the entities in a graph.

**`Python`**

```python Python
from zep_cloud.client import Zep

client = Zep(api_key=API_KEY)

# 20 entities per page. The pager fetches the next page when you iterate past the current page.
for node in client.graph.node.list(graph_uuid, page_size=20):
    print(node.name, node.created_at)
```

**`TypeScript`**

```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";

const client = new ZepClient({ apiKey: API_KEY });

// 20 entities per page. The pager fetches the next page when you iterate past the current page.
const nodes = await client.graph.node.list(graphUuid, { pageSize: 20 });

for await (const node of nodes) {
  console.log(node.name, node.createdAt);
}
```

**`Go`**

```go Go
import (
    "context"
    "fmt"
    "log"

    zep "github.com/getzep/zep-go/v4"
    zepclient "github.com/getzep/zep-go/v4/client"
    "github.com/getzep/zep-go/v4/graph"
    "github.com/getzep/zep-go/v4/option"
)

ctx := context.TODO()

client := zepclient.NewClient(
    option.WithAPIKey(apiKey),
)

// 20 entities per page. The iterator fetches the next page when it reaches the end of the current page.
page, err := client.Graph.Node.List(ctx, graphUUID, &graph.NodeListRequest{
    PageSize: zep.Int(20),
})
if err != nil {
    log.Fatal(err)
}

iter := page.Iterator()
for iter.Next(ctx) {
    node := iter.Current()
    fmt.Println(*node.Name, *node.CreatedAt)
}
if err := iter.Err(); err != nil {
    log.Fatal(err)
}
```

See [Entities](/entities) for per-type detail.

To rank nodes by how connected they are, pass `order_by="degree"` on `graph.node.list`. Each returned node then carries a `degree` field: the count of live entity edges that touch it. The count spans the whole graph and does not change when `filters` narrows which nodes come back. A node has the `degree` field only when the request orders by degree.

**`Python`**

```python Python
# The most-connected entities in a graph.
nodes = client.graph.node.list(
    graph_uuid, order_by="degree", order="desc", page_size=20
).items or []
for node in nodes:
    print(node.name, node.degree)
```

**`TypeScript`**

```typescript TypeScript
// The most-connected entities in a graph.
const nodes = await client.graph.node.list(graphUuid, {
  orderBy: "degree",
  order: "desc",
  pageSize: 20,
});

for (const node of nodes.data) {
  console.log(node.name, node.degree);
}
```

**`Go`**

```go Go
// The most-connected entities in a graph.
page, err := client.Graph.Node.List(ctx, graphUUID, &graph.NodeListRequest{
    OrderBy:  graph.NodeListRequestOrderByDegree.Ptr(),
    Order:    graph.NodeListRequestOrderDesc.Ptr(),
    PageSize: zep.Int(20),
})
if err != nil {
    log.Fatal(err)
}

for _, node := range page.Results {
    fmt.Println(*node.Name, *node.Degree)
}
```

### Listing edges with filters

Pass a `filters` object to narrow the results. The example below lists facts on a graph that were created in July 2025 and use specific edge types.

**`Python`**

```python Python
from zep_cloud.client import Zep

client = Zep(api_key=API_KEY)

edges = client.graph.edge.list(
    graph_uuid,
    filters={
        "edge_types": ["WORKS_WITH", "COLLABORATES_ON"],
        "date_filters": {
            "any_of": [
                {
                    "all_of": [
                        {"field": "created_at", "operator": "gte", "value": "2025-07-01T00:00:00Z"},
                        {"field": "created_at", "operator": "lt", "value": "2025-08-01T00:00:00Z"},
                    ]
                }
            ]
        },
    },
    page_size=50,
)

for edge in edges:
    print(edge.fact, edge.created_at)
```

**`TypeScript`**

```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";

const client = new ZepClient({ apiKey: API_KEY });

const edges = await client.graph.edge.list(graphUuid, {
  pageSize: 50,
  filters: {
    edgeTypes: ["WORKS_WITH", "COLLABORATES_ON"],
    dateFilters: {
      anyOf: [
        {
          allOf: [
            { field: "created_at", operator: "gte", value: new Date("2025-07-01T00:00:00Z") },
            { field: "created_at", operator: "lt", value: new Date("2025-08-01T00:00:00Z") },
          ],
        },
      ],
    },
  },
});

for await (const edge of edges) {
  console.log(edge.fact, edge.createdAt);
}
```

**`Go`**

```go Go
page, err := client.Graph.Edge.List(ctx, graphUUID, &graph.EdgeListRequest{
    PageSize: zep.Int(50),
    Filters: &zep.EdgeListFilters{
        EdgeTypes: []string{"WORKS_WITH", "COLLABORATES_ON"},
        DateFilters: &zep.DateFilters{
            AnyOf: []*zep.DateFilterGroup{{
                AllOf: []*zep.DateFilter{
                    {
                        Field:    zep.DateFilterFieldCreatedAt.Ptr(),
                        Operator: zep.DateFilterOperatorGte.Ptr(),
                        Value:    zep.Time(zep.MustParseDateTime("2025-07-01T00:00:00Z")),
                    },
                    {
                        Field:    zep.DateFilterFieldCreatedAt.Ptr(),
                        Operator: zep.DateFilterOperatorLt.Ptr(),
                        Value:    zep.Time(zep.MustParseDateTime("2025-08-01T00:00:00Z")),
                    },
                },
            }},
        },
    },
})
if err != nil {
    log.Fatal(err)
}

iter := page.Iterator()
for iter.Next(ctx) {
    edge := iter.Current()
    fmt.Println(*edge.Fact, *edge.CreatedAt)
}
if err := iter.Err(); err != nil {
    log.Fatal(err)
}
```

The `date_filters` object uses the same `any_of`/`all_of` structure as graph search: predicates inside one group are ANDed and groups are ORed. See [Datetime Filtering](/searching-the-graph#datetime-filtering) for the full semantics.

### Listing observations and thread summaries

Observations and thread summaries use the same shared parameters.

**`Python`**

```python Python
# Observations in a graph.
observations = client.graph.observation.list(graph_uuid, page_size=20)

# Thread summaries across the threads of a user graph.
summaries = client.graph.thread_summary.list(graph_uuid, page_size=20)
```

**`TypeScript`**

```typescript TypeScript
// Observations in a graph.
const observations = await client.graph.observation.list(graphUuid, {
  pageSize: 20,
});

// Thread summaries across the threads of a user graph.
const summaries = await client.graph.threadSummary.list(graphUuid, {
  pageSize: 20,
});
```

**`Go`**

```go Go
// Observations in a graph.
observations, err := client.Graph.Observation.List(ctx, graphUUID, &graph.ObservationListRequest{
    PageSize: zep.Int(20),
})
if err != nil {
    log.Fatal(err)
}

// Thread summaries across the threads of a user graph.
summaries, err := client.Graph.ThreadSummary.List(ctx, graphUUID, &graph.ThreadSummaryListRequest{
    PageSize: zep.Int(20),
})
if err != nil {
    log.Fatal(err)
}
```

Read [Observations](/observations) and [Thread summaries](/thread-summaries) for details.

[Document summaries](/documents) apply to episodes grouped by `document_id`. List them with `graph.document_summary.list`.

### Listing episodes

`graph.episode.list` takes the same `page_size` and `cursor` parameters as the other artifact types, and returns the newest episodes first. Use it to walk every episode in a graph in stable pages. See [Pagination](#pagination).

The episode list accepts three filter keys only: `mentioned_node_uuids`, which restricts results to episodes mentioning any of the listed entities, `metadata_filters`, which restricts results to episodes whose stored metadata matches the same predicate used by graph search, and `content_policy_violated` (see [List filters](#list-filters)).

**`Python`**

```python Python
# Episodes that mention a given entity.
episodes = client.graph.episode.list(
    graph_uuid,
    filters={"mentioned_node_uuids": [node_uuid]},
    page_size=50,
)

for episode in episodes:
    print(episode.uuid_, episode.reference_time)
```

**`TypeScript`**

```typescript TypeScript
// Episodes that mention a given entity.
const episodes = await client.graph.episode.list(graphUuid, {
  pageSize: 50,
  filters: { mentionedNodeUuids: [nodeUuid] },
});

for await (const episode of episodes) {
  console.log(episode.uuid, episode.referenceTime);
}
```

**`Go`**

```go Go
// Episodes that mention a given entity.
page, err := client.Graph.Episode.List(ctx, graphUUID, &graph.EpisodeListRequest{
    PageSize: zep.Int(50),
    Filters:  &zep.EpisodeListFilters{
        MentionedNodeUUIDs: []string{nodeUUID},
    },
})
if err != nil {
    log.Fatal(err)
}

iter := page.Iterator()
for iter.Next(ctx) {
    episode := iter.Current()
    fmt.Println(*episode.UUID, *episode.ReferenceTime)
}
if err := iter.Err(); err != nil {
    log.Fatal(err)
}
```

## Pagination

Python and TypeScript list and search methods return a pager. Go methods return
one page. The Go `page.Iterator()` method reads every page. `page_size` sets the
number of items in one page. It does not set the total number of items.

Python `.items` and TypeScript `.data` contain only the first page. Iterate over
a pager to read every page. The cursor is opaque. Do not create or change it.

To read the top N items, set `page_size=N` and read the first page. In Python,
you can also use `itertools.islice(pager, N)` to stop after N items. Search
ranks a limited set of results, so iteration over a search pager ends after that
set.

**`Python`**

```python Python
from itertools import islice

pager = client.graph.node.list(graph_uuid, page_size=20)
first_page = pager.items or []
top_ten = list(
    islice(client.graph.node.list(graph_uuid, page_size=10), 10)
)

for node in pager:
    print(node.name)

all_nodes = list(client.graph.node.list(graph_uuid, page_size=100))
```

**`TypeScript`**

```typescript TypeScript
const pager = await client.graph.node.list(graphUuid, {
  pageSize: 20,
});
const firstPage = pager.data;

for await (const node of pager) {
  console.log(node.name);
}
```

**`Go`**

```go Go
page, err := client.Graph.Node.List(ctx, graphUUID, &graph.NodeListRequest{
    PageSize: zep.Int(20),
})
if err != nil {
    return err
}

iterator := page.Iterator()
for iterator.Next(ctx) {
    node := iterator.Current()
    fmt.Println(*node.Name)
}
if err := iterator.Err(); err != nil {
    return err
}
```

## Navigating the graph

Listing walks a graph by artifact type. Navigation walks it by connection: start from a node and pull what it is attached to. When the API key has no active attribute constraints, both methods include the connecting edges' endpoint names and labels (`source_node_name`, `target_node_name`, `source_node_labels`, `target_node_labels`), avoiding separate endpoint-node reads. With active attribute constraints, Zep omits these four fields and retains the endpoint UUIDs.

### Neighbors of a node

`graph.node.list_neighbors` returns each distinct node connected to an anchor node, together with every edge that connects it to the anchor. Results paginate by neighbor node with the same pager as the list methods (see [Pagination](#pagination)).

| Parameter   | Type            | Description                                                                                                                                                                                             | Default  |
| ----------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `direction` | string          | Orientation of the connecting edge relative to the anchor: `"out"`, `"in"`, or `"both"`.                                                                                                                | `"both"` |
| `filters`   | `SearchFilters` | Constrains the connecting edges (edge types, dates, and the [node and episode filters](/searching-the-graph#node-and-episode-filtering)) and the neighbor nodes (`node_labels`, `exclude_node_labels`). | –        |
| `page_size` | integer         | Maximum neighbor nodes per page, 1–100.                                                                                                                                                                 | `50`     |
| `cursor`    | string          | Opaque cursor for the next page.                                                                                                                                                                        | –        |

**`Python`**

```python Python
neighbors = client.graph.node.list_neighbors(
    graph_uuid,
    node_uuid,
    direction="both",
    page_size=25,
)

for neighbor in neighbors:
    print(neighbor.node.name, len(neighbor.edges))
```

**`TypeScript`**

```typescript TypeScript
const neighbors = await client.graph.node.listNeighbors(graphUuid, nodeUuid, {
  direction: "both",
  pageSize: 25,
});

for await (const neighbor of neighbors) {
  console.log(neighbor.node?.name, neighbor.edges?.length);
}
```

**`Go`**

```go Go
direction := graph.NeighborsRequestDirectionBoth

page, err := client.Graph.Node.ListNeighbors(ctx, graphUUID, nodeUUID, &graph.NeighborsRequest{
    Direction: &direction,
    PageSize:  zep.Int(25),
})
if err != nil {
    log.Fatal(err)
}

iter := page.Iterator()
for iter.Next(ctx) {
    neighbor := iter.Current()
    fmt.Println(*neighbor.Node.Name, len(neighbor.Edges))
}
if err := iter.Err(); err != nil {
    log.Fatal(err)
}
```

### Bounded subgraphs

`graph.get_subgraph` expands breadth-first from up to 20 seed nodes and returns the resulting neighborhood as a single `{nodes, edges}` payload. Every edge's endpoints are present in `nodes`, so the response is a self-contained graph you can render directly. It is built for agent exploration and visualization, not for exporting a graph. Use the list methods for that.

| Parameter         | Type            | Description                                                                                                                                                                              | Default  |
| ----------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `graph_uuid`      | string          | Target graph.                                                                                                                                                                            | –        |
| `seed_node_uuids` | array           | Nodes to expand from, 1–20 entries. Seeds are admitted in request order. Seeds that do not exist are ignored.                                                                            | –        |
| `depth`           | integer         | Maximum hops from the seeds, 1–3.                                                                                                                                                        | `1`      |
| `direction`       | string          | Edge orientation followed during expansion: `"out"`, `"in"`, or `"both"`.                                                                                                                | `"both"` |
| `filters`         | `SearchFilters` | Constrains traversed edges and included nodes. A node excluded by a filter is not expanded through. `metadata_filters` is rejected here, because it cannot be enforced during traversal. | –        |
| `max_nodes`       | integer         | Node budget, 1–500. Seeds count against it.                                                                                                                                              | `100`    |
| `max_edges`       | integer         | Edge budget, 1–1000.                                                                                                                                                                     | `200`    |

When a budget stops the expansion, the response sets `truncated` to `true` and names the binding limit in `truncation_reason` (for example `max_nodes` or `max_edges`), so a partial neighborhood is never mistaken for a complete one.

**`Python`**

```python Python
subgraph = client.graph.get_subgraph(
    graph_uuid,
    seed_node_uuids=[node_uuid],
    depth=2,
    max_nodes=200,
)

print(len(subgraph.nodes), len(subgraph.edges))
if subgraph.truncated:
    print("truncated by:", subgraph.truncation_reason)
```

**`TypeScript`**

```typescript TypeScript
const subgraph = await client.graph.getSubgraph(graphUuid, {
  seedNodeUuids: [nodeUuid],
  depth: 2,
  maxNodes: 200,
});

console.log(subgraph.nodes?.length, subgraph.edges?.length);
if (subgraph.truncated) {
  console.log("truncated by:", subgraph.truncationReason);
}
```

**`Go`**

```go Go
subgraph, err := client.Graph.GetSubgraph(ctx, graphUUID, &zep.SubgraphRequest{
    SeedNodeUUIDs: []string{nodeUUID},
    Depth:         zep.Int(2),
    MaxNodes:      zep.Int(200),
})
if err != nil {
    log.Fatal(err)
}

fmt.Println(len(subgraph.Nodes), len(subgraph.Edges))
if subgraph.Truncated != nil && *subgraph.Truncated {
    fmt.Println("truncated by:", *subgraph.TruncationReason)
}
```

### Reads by node or episode

To read the items that are connected to one node or one episode, use a list method with a filter:

| To read                           | Use                                                                                                                     |
| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| The edges of a node               | `graph.edge.list` with `connected_node_uuids`, or `graph.node.list_neighbors`.                                          |
| The episodes that mention a node  | The `episode_uuids` field on the node object, or `graph.episode.list` with `mentioned_node_uuids` for the complete set. |
| The nodes and edges of an episode | `graph.node.list` and `graph.edge.list` with `episode_uuids`.                                                           |

## Related

* [Searching the graph](/searching-the-graph) — rank artifacts by relevance to a query, including the full `SearchFilters` reference.
* [Entities](/entities), [Facts](/facts), [Observations](/observations), and [Thread summaries](/thread-summaries) — per-type detail for each artifact.