> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs-beta.getzep.com/v4/adding-fact-triplets/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs-beta.getzep.com/_mcp/server. # Manually Updating the Graph ## Overview Zep provides several ways to manually change a graph: * Use `graph.node.add` to add one or more nodes without creating relationships. * Use `graph.edge.add` to add a relationship and its source and target nodes together. * Use [hyperedges](/hyperedges) to store one fact across more than two nodes. * Use `graph.node.update` to update an existing node by UUID. * Use `graph.edge.update` to update an existing edge by UUID. Each method takes the `graph_uuid` of the graph. For a user graph, use the `graph_uuid` from the user create response. For a shared Context Graph, use the `uuid` from the graph create response. #### [Seed canonical graph data with zep-ingest](/zep-ingest#build-the-pipeline-step-by-step) Use `ingest_nodes` and `ingest_fact_triples` to seed known entities and relationships before ingesting source material. ## Adding Nodes Use `graph.node.add` when you already have structured entity data and do not need Zep to extract nodes from an episode or create relationships between them. Zep stores the node contents you provide and derives the search representation from each node's name. The same method handles both single-node and batch creation. Each request must contain between 1 and 100 nodes and targets the one graph in its `graph_uuid` argument. ### Adding a Single Node Pass one item in the `nodes` list to add a single node: **`Python`** ```python Python from zep_cloud.client import Zep from zep_cloud.types import NodeInput client = Zep(api_key=API_KEY) result = client.graph.node.add( graph_uuid, nodes=[ NodeInput( name="Acme Corp", summary="Enterprise customer on the annual plan.", label="Company", attributes={"industry": "manufacturing"}, ), ], ) # Zep assigns the node UUIDs before the asynchronous task begins. print(result.nodes[0].uuid_) print(result.task.uuid_) ``` **`TypeScript`** ```typescript TypeScript import { ZepClient } from "@getzep/zep-cloud"; const client = new ZepClient({ apiKey: API_KEY }); const result = await client.graph.node.add(graphUuid, { nodes: [ { name: "Acme Corp", summary: "Enterprise customer on the annual plan.", label: "Company", attributes: { industry: "manufacturing" }, }, ], }); // Zep assigns the node UUIDs before the asynchronous task begins. console.log(result.nodes?.[0].uuid); console.log(result.task?.uuid); ``` **`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)) result, err := client.Graph.Node.Add(ctx, graphUUID, &graph.AddNodesRequest{ Nodes: []*zep.NodeInput{ { Name: "Acme Corp", Summary: zep.String("Enterprise customer on the annual plan."), Label: zep.String("Company"), Attributes: map[string]any{"industry": "manufacturing"}, }, }, }) if err != nil { log.Fatal(err) } // Zep assigns the node UUIDs before the asynchronous task begins. fmt.Println(*result.Nodes[0].UUID) fmt.Println(*result.Task.UUID) ``` ### Adding Nodes in a Batch Pass up to 100 items in the `nodes` list to add nodes in a batch: **`Python`** ```python Python result = client.graph.node.add( graph_uuid, nodes=[ NodeInput(name="Alice Smith", label="Person", summary="Account owner at Acme Corp."), NodeInput(name="Bob Jones", label="Person", summary="Support engineer."), NodeInput(name="Atlas Gateway", label="Product"), ], ) for node in result.nodes: print(node.name, node.uuid_) ``` **`TypeScript`** ```typescript TypeScript const result = await client.graph.node.add(graphUuid, { nodes: [ { name: "Alice Smith", label: "Person", summary: "Account owner at Acme Corp." }, { name: "Bob Jones", label: "Person", summary: "Support engineer." }, { name: "Atlas Gateway", label: "Product" }, ], }); for (const node of result.nodes ?? []) { console.log(node.name, node.uuid); } ``` **`Go`** ```go Go result, err := client.Graph.Node.Add(ctx, graphUUID, &graph.AddNodesRequest{ Nodes: []*zep.NodeInput{ {Name: "Alice Smith", Label: zep.String("Person"), Summary: zep.String("Account owner at Acme Corp.")}, {Name: "Bob Jones", Label: zep.String("Person"), Summary: zep.String("Support engineer.")}, {Name: "Atlas Gateway", Label: zep.String("Product")}, }, }) if err != nil { log.Fatal(err) } for _, node := range result.Nodes { fmt.Println(node.Name, *node.UUID) } ``` ### Node creation behavior `graph.node.add` returns the accepted nodes and a `task` immediately. Each returned node includes the UUID Zep assigned to it, but the node is not available to read or search until the asynchronous task succeeds. See [Check data ingestion status](/check-data-ingestion-status#checking-operation-status-with-task-polling) for polling instructions. Zep assigns node UUIDs. The node input has no `uuid` field. Every accepted node is a new node, and nodes are not deduplicated by name or content, so two identical requests create two sets of nodes. Keep the UUIDs from the response if you need to reference the nodes later. To protect a request against retries, you can send an idempotency key with the request. > **Note** > > To change a node after it is created (rename it, replace its summary, or edit attributes), use `graph.node.update` with a UUID from the `graph.node.add` response. Each node supports the following fields: | Field | Required | Description | | ------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `name` | Yes | Node name, 1 to 50 characters. | | `summary` | No | Node summary, up to 500 characters. | | `label` | No | A single entity type. The base `Entity` label is implicit. If the label matches an ontology type with properties, attributes are validated against it. | | `attributes` | No | Custom attributes. When `label` matches an ontology type with properties, the attributes are validated against it. | | `metadata` | No | Up to 10 metadata fields attached to the node's provenance episode. Zep does not store them as node metadata. | ## Updating a Node Use `graph.node.update` to update an existing node by UUID. You can change its `name`, `summary`, and `attributes`. Only the fields you provide are changed, and the updated node is returned synchronously. **`Python`** ```python Python from zep_cloud.client import Zep client = Zep(api_key=API_KEY) node = client.graph.node.update( graph_uuid, node_uuid, name="Acme Corporation", summary="Enterprise customer on the annual plan since 2024.", attributes={"industry": "manufacturing", "region": None}, ) print(node.name, node.attributes) ``` **`TypeScript`** ```typescript TypeScript import { ZepClient } from "@getzep/zep-cloud"; const client = new ZepClient({ apiKey: API_KEY }); const node = await client.graph.node.update(graphUuid, nodeUuid, { name: "Acme Corporation", summary: "Enterprise customer on the annual plan since 2024.", attributes: { industry: "manufacturing", region: null }, }); console.log(node.name, node.attributes); ``` **`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)) node, err := client.Graph.Node.Update(ctx, graphUUID, nodeUUID, &graph.PatchNodeRequest{ Name: zep.String("Acme Corporation"), Summary: zep.String("Enterprise customer on the annual plan since 2024."), Attributes: map[string]any{"industry": "manufacturing", "region": nil}, }) if err != nil { log.Fatal(err) } fmt.Println(*node.Name, node.Attributes) ``` A `summary` can be up to 5,000 characters. Node attributes are merged with the existing attributes. Set an attribute to `null` to delete it. ## Updating an Edge Use `graph.edge.update` to update an existing entity edge by UUID. You can change its `fact` and its `attributes`. The source and target node UUIDs cannot be changed. **`Python`** ```python Python from zep_cloud.client import Zep client = Zep(api_key=API_KEY) edge = client.graph.edge.update( graph_uuid, edge_uuid, fact="Alice Smith is the primary account owner at Acme Corp.", attributes={"start_year": 2023}, ) print(edge.fact, edge.attributes) ``` **`TypeScript`** ```typescript TypeScript import { ZepClient } from "@getzep/zep-cloud"; const client = new ZepClient({ apiKey: API_KEY }); const edge = await client.graph.edge.update(graphUuid, edgeUuid, { fact: "Alice Smith is the primary account owner at Acme Corp.", attributes: { start_year: 2023 }, }); console.log(edge.fact, edge.attributes); ``` **`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)) edge, err := client.Graph.Edge.Update(ctx, graphUUID, edgeUUID, &graph.PatchEdgeRequest{ Fact: zep.String("Alice Smith is the primary account owner at Acme Corp."), Attributes: map[string]any{"start_year": 2023}, }) if err != nil { log.Fatal(err) } fmt.Println(*edge.Fact, edge.Attributes) ``` Edge attributes are merged with the existing attributes. Set an attribute to `null` to delete it. ## Adding a Fact Triplet You can add manually specified fact/node triplets to the graph with `graph.edge.add`. One call accepts 1 to 100 edges. Each edge carries the fact, a relationship name (`fact_name`), and a `source_node` and a `target_node`. Each node reference contains a node `name` or a node `uuid`. When `deduplicate` is false (the default), a `name` creates a node. When `deduplicate` is true, Zep matches a node by name first, and can merge a duplicate edge or invalidate a contradicted edge. If the new fact invalidates an existing fact, Zep marks the existing fact as invalid and adds the new fact triplet. The `graph.edge.add` method returns the accepted `edges` in request order and a `task` that you can use to track the operation. See [Check data ingestion status](/check-data-ingestion-status#checking-operation-status-with-task-polling) for polling instructions. **`Python`** ```python Python from zep_cloud.client import Zep from zep_cloud.types import EdgeInput, EdgeNodeRef client = Zep(api_key=API_KEY) result = client.graph.edge.add( graph_uuid, edges=[ EdgeInput( fact="Paul met Eric", fact_name="MET", source_node=EdgeNodeRef(name="Paul", summary="Paul is a software engineer."), target_node=EdgeNodeRef(name="Eric", summary="Eric is a product manager."), valid_at="2024-01-15T10:30:00Z", ) ], ) # The result includes the edge UUID and a task for tracking processing status print(result.edges[0].uuid_) print(result.task.uuid_) ``` **`TypeScript`** ```typescript TypeScript import { ZepClient } from "@getzep/zep-cloud"; const client = new ZepClient({ apiKey: API_KEY }); const result = await client.graph.edge.add(graphUuid, { edges: [ { fact: "Paul met Eric", factName: "MET", sourceNode: { name: "Paul", summary: "Paul is a software engineer." }, targetNode: { name: "Eric", summary: "Eric is a product manager." }, validAt: "2024-01-15T10:30:00Z", }, ], }); // The result includes the edge UUID and a task for tracking processing status console.log(result.edges?.[0]?.uuid); console.log(result.task?.uuid); ``` **`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)) result, err := client.Graph.Edge.Add(ctx, graphUUID, &graph.AddEdgesRequest{ Edges: []*zep.EdgeInput{ { Fact: "Paul met Eric", FactName: "MET", SourceNode: &zep.EdgeNodeRef{ Name: zep.String("Paul"), Summary: zep.String("Paul is a software engineer."), }, TargetNode: &zep.EdgeNodeRef{ Name: zep.String("Eric"), Summary: zep.String("Eric is a product manager."), }, ValidAt: zep.String("2024-01-15T10:30:00Z"), }, }, }) if err != nil { log.Fatal(err) } // The result includes the edge UUID and a task for tracking processing status fmt.Println(*result.Edges[0].UUID) fmt.Println(*result.Task.UUID) ``` ### Retrieving Created UUIDs Each edge UUID is in the `graph.edge.add` response, one acknowledgement per edge in request order. When `deduplicate` is false, each acknowledgement also carries `source_node_uuid` and `target_node_uuid`: the UUID you supplied, or the UUID that Zep assigned to a node it created. When `deduplicate` is true, Zep resolves the endpoints during the asynchronous task. After the task completes, call `task.get` with the task UUID. The task `result` contains an `edges` list, and each entry has `edge_uuid`, `source_node_uuid`, and `target_node_uuid`. **`Python`** ```python Python import time task = client.task.get(result.task.uuid_) while task.completed_at is None: time.sleep(1) task = client.task.get(result.task.uuid_) print(task.status) print(task.result["edges"][0]["edge_uuid"]) print(task.result["edges"][0]["source_node_uuid"]) print(task.result["edges"][0]["target_node_uuid"]) ``` **`TypeScript`** ```typescript TypeScript const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); let task = await client.task.get(result.task!.uuid!); while (task.completedAt === undefined) { await sleep(1_000); task = await client.task.get(result.task!.uuid!); } console.log(task.status); console.log(task.result?.edges?.[0]?.edge_uuid); console.log(task.result?.edges?.[0]?.source_node_uuid); console.log(task.result?.edges?.[0]?.target_node_uuid); ``` **`Go`** ```go Go task, err := client.Task.Get(ctx, *result.Task.UUID) if err != nil { log.Fatal(err) } for task.CompletedAt == nil { time.Sleep(time.Second) task, err = client.Task.Get(ctx, *result.Task.UUID) if err != nil { log.Fatal(err) } } fmt.Println(*task.Status) fmt.Println(task.Result["edges"].([]map[string]any)[0]["edge_uuid"]) fmt.Println(task.Result["edges"].([]map[string]any)[0]["source_node_uuid"]) fmt.Println(task.Result["edges"].([]map[string]any)[0]["target_node_uuid"]) ``` ### Custom types and attributes You can attach custom scalar attributes to nodes and edges, and optionally reference [custom entity and edge types](/customizing-graph-structure) from your ontology to enforce a schema. * `source_node.labels` / `target_node.labels`: specify a single **entity type name**. When it matches an ontology type with properties, the node attributes are validated against it. * `fact_name`: serves as both the relationship display name and the **edge type name** looked up in your ontology. * `source_node.attributes` / `target_node.attributes` / `attributes`: custom scalar properties on those nodes and on the edge, usable for [filtering in graph searches](/searching-the-graph#property-filtering). **Attribute validation**: specifying types is optional. If a label or `fact_name` is not found in your ontology, attributes pass through without validation: | Situation | Result | | -------------------------------------------------- | ----------------------------------------------- | | Label not in ontology | All node attributes pass through, with no error | | `fact_name` not in ontology | All edge attributes pass through, with no error | | Label in ontology, matching type has no properties | All attributes pass through | | Label in ontology and type HAS properties defined | Attributes validated strictly | When validation activates, every attribute key and value type must match the schema. Otherwise the call fails with HTTP 400. **`Python`** ```python Python from zep_cloud.client import Zep from zep_cloud.types import EdgeNodeRef client = Zep(api_key=API_KEY) # Assumes your ontology defines: # - Entity type "Person" with properties: { "age": int, "role": text } # - Edge type "WORKS_AT" with properties: { "start_year": int, "department": text } result = client.graph.edge.add( graph_uuid, edges=[ EdgeInput( fact="Alice works at Acme Corp in the engineering department", fact_name="WORKS_AT", source_node=EdgeNodeRef( name="Alice", labels=["Person"], attributes={"age": 34, "role": "Staff Engineer"}, ), target_node=EdgeNodeRef(name="Acme Corp"), attributes={"start_year": 2021, "department": "engineering"}, ) ], ) ``` **`TypeScript`** ```typescript TypeScript import { ZepClient } from "@getzep/zep-cloud"; const client = new ZepClient({ apiKey: API_KEY }); // Assumes your ontology defines: // - Entity type "Person" with properties: { "age": int, "role": text } // - Edge type "WORKS_AT" with properties: { "start_year": int, "department": text } const result = await client.graph.edge.add(graphUuid, { edges: [ { fact: "Alice works at Acme Corp in the engineering department", factName: "WORKS_AT", sourceNode: { name: "Alice", labels: ["Person"], attributes: { age: 34, role: "Staff Engineer" }, }, targetNode: { name: "Acme Corp" }, attributes: { start_year: 2021, department: "engineering" }, }, ], }); ``` **`Go`** ```go Go // Assumes your ontology defines: // - Entity type "Person" with properties: { "age": int, "role": text } // - Edge type "WORKS_AT" with properties: { "start_year": int, "department": text } result, err := client.Graph.Edge.Add(ctx, graphUUID, &graph.AddEdgesRequest{ Edges: []*zep.EdgeInput{ { Fact: "Alice works at Acme Corp in the engineering department", FactName: "WORKS_AT", SourceNode: &zep.EdgeNodeRef{ Name: zep.String("Alice"), Labels: []string{"Person"}, Attributes: map[string]any{"age": 34, "role": "Staff Engineer"}, }, TargetNode: &zep.EdgeNodeRef{ Name: zep.String("Acme Corp"), }, Attributes: map[string]any{"start_year": 2021, "department": "engineering"}, }, }, }) if err != nil { log.Fatal(err) } ``` > **Note** > > Attribute values must be scalar types: string, number, boolean, or null. Nested objects and arrays are not supported. ### Fact triplet metadata You can attach metadata to a fact triple. Zep attaches the metadata to the provenance episode of the fact triple, which makes the resulting edge and nodes filterable via [episode metadata filters](/searching-the-graph#episode-metadata-filtering) in graph search. Zep does not store the metadata as edge or node metadata. See [Episode metadata](/adding-business-data#episode-metadata) for full details on metadata constraints and update semantics. Metadata values must be scalars (string, number, boolean, or null) or non-empty arrays of scalars. A maximum of 10 keys are allowed. **`Python`** ```python Python result = client.graph.edge.add( graph_uuid, edges=[ EdgeInput( fact="Alice approved the Q3 budget", fact_name="APPROVED", source_node=EdgeNodeRef(name="Alice"), target_node=EdgeNodeRef(name="Q3 budget"), metadata={"source": "finance_system", "priority": 1}, ) ], ) ``` **`TypeScript`** ```typescript TypeScript const result = await client.graph.edge.add(graphUuid, { edges: [ { fact: "Alice approved the Q3 budget", factName: "APPROVED", sourceNode: { name: "Alice" }, targetNode: { name: "Q3 budget" }, metadata: { source: "finance_system", priority: 1 }, }, ], }); ``` **`Go`** ```go Go result, err := client.Graph.Edge.Add(ctx, graphUUID, &graph.AddEdgesRequest{ Edges: []*zep.EdgeInput{ { Fact: "Alice approved the Q3 budget", FactName: "APPROVED", SourceNode: &zep.EdgeNodeRef{Name: zep.String("Alice")}, TargetNode: &zep.EdgeNodeRef{Name: zep.String("Q3 budget")}, Metadata: map[string]any{"source": "finance_system", "priority": 1}, }, }, }) if err != nil { log.Fatal(err) } ``` ### Specifying Node UUIDs You can optionally specify `source_node.uuid` and/or `target_node.uuid` to control which nodes are used. The behavior depends on whether you provide a UUID: | Scenario | Behavior | | --------------------------------- | --------------------------------------------------------------------------------------------------------------- | | **UUID provided and node exists** | Uses the existing node with that UUID | | **UUID omitted** | Searches for an existing node by name. If no match is found, creates a new node with a UUID that Zep generates. | Use only a node UUID that you read from the same graph. ### Field Limits | Field | Limit | | -------------------------------------------- | --------------------------------------------------------- | | `fact` | Maximum 250 characters | | `fact_name` | Maximum 50 characters; must be `SCREAMING_SNAKE_CASE` | | `source_node.name`, `target_node.name` | Maximum 50 characters each | | `source_node.summary`, `target_node.summary` | Maximum 500 characters each | | `valid_at`, `invalid_at`, `expired_at` | RFC 3339 / ISO 8601 format (e.g., `2024-01-15T10:30:00Z`) | > Add nodes and fact triplets, or update existing nodes and edges