Skip to navigation

Reading Data from the Graph

Read nodes, edges, episodes, and graph neighborhoods

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

from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
edge = client.graph.edge.get(graph_uuid, edge_uuid)

Reading nodes

from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
node = client.graph.node.get(graph_uuid, node_uuid)

Reading Episodes

from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
episode = client.graph.episode.get(graph_uuid, episode_uuid)

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 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:

ArtifactMethodDetail
Nodes (entities)graph.node.listEntities
Edges (facts)graph.edge.listFacts
Episodesgraph.episode.listListing episodes
Observationsgraph.observation.listObservations
Thread summariesgraph.thread_summary.listThread summaries
Document summariesgraph.document_summary.listDocuments

Shared parameters

ParameterTypeDescriptionDefault
filtersobjectRestrict which artifacts are returned. Zep sends it in the request body and applies it on the server. See List filters.–
page_sizeintegerMaximum number of items per page, 1–100.50
cursorstringOpaque cursor for the next page. The SDK pager sends it for you (see 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 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.

Listing nodes

List the entities in a graph.

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)

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

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

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.

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)

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 for the full semantics.

Listing observations and thread summaries

Observations and thread summaries use the same shared parameters.

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

Read Observations and Thread summaries for details.

Document summaries 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.

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

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

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.

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))

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

ParameterTypeDescriptionDefault
directionstringOrientation of the connecting edge relative to the anchor: "out", "in", or "both"."both"
filtersSearchFiltersConstrains the connecting edges (edge types, dates, and the node and episode filters) and the neighbor nodes (node_labels, exclude_node_labels).–
page_sizeintegerMaximum neighbor nodes per page, 1–100.50
cursorstringOpaque cursor for the next page.–
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))

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.

ParameterTypeDescriptionDefault
graph_uuidstringTarget graph.–
seed_node_uuidsarrayNodes to expand from, 1–20 entries. Seeds are admitted in request order. Seeds that do not exist are ignored.–
depthintegerMaximum hops from the seeds, 1–3.1
directionstringEdge orientation followed during expansion: "out", "in", or "both"."both"
filtersSearchFiltersConstrains 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_nodesintegerNode budget, 1–500. Seeds count against it.100
max_edgesintegerEdge 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.

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)

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 readUse
The edges of a nodegraph.edge.list with connected_node_uuids, or graph.node.list_neighbors.
The episodes that mention a nodeThe 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 episodegraph.node.list and graph.edge.list with episode_uuids.