> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs-beta.getzep.com/v3/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.add_nodes` to add one or more nodes without creating relationships. * Use `graph.add_fact_triple` to add a relationship and its source and target nodes together. * Use `graph.node.update` to update an existing node by UUID. * Use `graph.edge.update` to update an existing edge by UUID. #### [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 `add_nodes` 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 target one `user_id` or `graph_id`. ### Adding a Single Node Pass one item in the `nodes` list to add a single node: **`Python`** ```python Python from zep_cloud import AddNodeItem from zep_cloud.client import Zep client = Zep(api_key=API_KEY) result = client.graph.add_nodes( user_id=user_id, # Optional: You can use graph_id instead of user_id nodes=[ AddNodeItem( name="Alice Johnson", summary="An engineer on the platform team.", label="Person", attributes={"role": "Engineer", "active": True}, metadata={"source": "crm"}, ) ], ) # UUIDs are assigned before the asynchronous task begins. node_uuid = result.nodes[0].uuid_ task_id = result.task_id ``` **`TypeScript`** ```typescript TypeScript import { ZepClient } from "@getzep/zep-cloud"; const client = new ZepClient({ apiKey: API_KEY }); const result = await client.graph.addNodes({ userId: userId, // Optional: You can use graphId instead of userId nodes: [ { name: "Alice Johnson", summary: "An engineer on the platform team.", label: "Person", attributes: { role: "Engineer", active: true }, metadata: { source: "crm" }, }, ], }); // UUIDs are assigned before the asynchronous task begins. const nodeUuid = result.nodes?.[0]?.uuid; const taskId = result.taskId; ``` **`Go`** ```go Go import ( "context" "log" "github.com/getzep/zep-go/v3" zepclient "github.com/getzep/zep-go/v3/client" "github.com/getzep/zep-go/v3/option" ) client := zepclient.NewClient(option.WithAPIKey(apiKey)) userID := "user123" summary := "An engineer on the platform team." label := "Person" result, err := client.Graph.AddNodes(context.TODO(), &zep.AddNodesRequest{ UserID: &userID, // Optional: You can use GraphID instead of UserID Nodes: []*zep.AddNodeItem{ { Name: "Alice Johnson", Summary: &summary, Label: &label, Attributes: map[string]interface{}{"role": "Engineer", "active": true}, Metadata: map[string]interface{}{"source": "crm"}, }, }, }) if err != nil { log.Fatalf("Failed to add node: %v", err) } // UUIDs are assigned before the asynchronous task begins. if len(result.Nodes) == 0 || result.Nodes[0].UUID == nil || result.TaskID == nil { log.Fatal("The add node response did not include the expected UUID and task ID") } log.Printf("Node UUID: %s", *result.Nodes[0].UUID) log.Printf("Task ID: %s", *result.TaskID) ``` ### Adding Nodes in a Batch Include multiple items in the same `nodes` list to create up to 100 nodes in one request. The nodes may use different entity types, but they must all belong to the same user or graph. **`Python`** ```python Python from zep_cloud import AddNodeItem from zep_cloud.client import Zep client = Zep(api_key=API_KEY) result = client.graph.add_nodes( graph_id=graph_id, # Optional: You can use user_id instead of graph_id nodes=[ AddNodeItem( name="Acme Corporation", summary="An enterprise software company.", label="Organization", attributes={"tier": "enterprise"}, ), AddNodeItem( name="Alice Johnson", label="Person", attributes={"role": "Engineer"}, ), ], ) node_uuids = [node.uuid_ for node in result.nodes] task_id = result.task_id ``` **`TypeScript`** ```typescript TypeScript import { ZepClient } from "@getzep/zep-cloud"; const client = new ZepClient({ apiKey: API_KEY }); const result = await client.graph.addNodes({ graphId: graphId, // Optional: You can use userId instead of graphId nodes: [ { name: "Acme Corporation", summary: "An enterprise software company.", label: "Organization", attributes: { tier: "enterprise" }, }, { name: "Alice Johnson", label: "Person", attributes: { role: "Engineer" }, }, ], }); const nodeUuids = result.nodes?.map((node) => node.uuid); const taskId = result.taskId; ``` **`Go`** ```go Go import ( "context" "log" "github.com/getzep/zep-go/v3" zepclient "github.com/getzep/zep-go/v3/client" "github.com/getzep/zep-go/v3/option" ) client := zepclient.NewClient(option.WithAPIKey(apiKey)) graphID := "crm-accounts" organizationSummary := "An enterprise software company." organizationLabel := "Organization" personLabel := "Person" result, err := client.Graph.AddNodes(context.TODO(), &zep.AddNodesRequest{ GraphID: &graphID, // Optional: You can use UserID instead of GraphID Nodes: []*zep.AddNodeItem{ { Name: "Acme Corporation", Summary: &organizationSummary, Label: &organizationLabel, Attributes: map[string]interface{}{"tier": "enterprise"}, }, { Name: "Alice Johnson", Label: &personLabel, Attributes: map[string]interface{}{"role": "Engineer"}, }, }, }) if err != nil { log.Fatalf("Failed to add nodes: %v", err) } nodeUUIDs := make([]string, 0, len(result.Nodes)) for _, node := range result.Nodes { if node.UUID == nil { log.Fatal("The add nodes response included a node without a UUID") } nodeUUIDs = append(nodeUUIDs, *node.UUID) } if result.TaskID == nil { log.Fatal("The add nodes response did not include a task ID") } log.Printf("Node UUIDs: %v", nodeUUIDs) log.Printf("Task ID: %s", *result.TaskID) ``` ### Node creation behavior `add_nodes` returns a `task_id` and the accepted nodes 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. You cannot supply one: a request that includes a node `uuid` is rejected with a `400`. 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. > **Note** > > To change a node after it is created — rename it, replace its summary, edit attributes, or clear its entity type — use `graph.node.update` with a UUID from the `add_nodes` response. Each node supports the following fields: | Field | Required | Description | | ------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | Yes | Node name, up to 50 characters. | | `summary` | No | Node summary, up to 500 characters. | | `label` | No | A single entity type, up to 100 characters. The base `Entity` label is implicit. If the label matches an ontology type with properties, attributes are validated against it. | | `attributes` | No | Up to 10 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. | | `created_at` | No | RFC 3339 creation time for the node. Defaults to the request time. | ## Updating a Node Use `graph.node.update` to update an existing node by UUID. 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) updated_node = client.graph.node.update( uuid_=node_uuid, name="Alice Smith", summary="A principal engineer on the platform team.", labels=["Person"], attributes={ "role": "Principal Engineer", "legacy_field": None, # Deletes this attribute }, ) ``` **`TypeScript`** ```typescript TypeScript import { ZepClient } from "@getzep/zep-cloud"; const client = new ZepClient({ apiKey: API_KEY }); const updatedNode = await client.graph.node.update(nodeUuid, { name: "Alice Smith", summary: "A principal engineer on the platform team.", labels: ["Person"], attributes: { role: "Principal Engineer", legacy_field: null, // Deletes this attribute }, }); ``` **`Go`** ```go Go import ( "context" "log" zepclient "github.com/getzep/zep-go/v3/client" graph "github.com/getzep/zep-go/v3/graph" "github.com/getzep/zep-go/v3/option" ) client := zepclient.NewClient(option.WithAPIKey(apiKey)) name := "Alice Smith" summary := "A principal engineer on the platform team." updatedNode, err := client.Graph.Node.Update( context.TODO(), nodeUUID, &graph.UpdateNodeRequest{ Name: &name, Summary: &summary, Labels: []string{"Person"}, Attributes: map[string]interface{}{ "role": "Principal Engineer", "legacy_field": nil, // Deletes this attribute }, }, ) if err != nil { log.Fatalf("Failed to update node: %v", err) } log.Printf("Updated node: %s", updatedNode.UUID) ``` 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. If you provide `labels`, the list replaces the existing labels. Python and TypeScript callers can use an empty list to clear the node's entity type. > **Note** > > The current Go SDK omits an empty `Labels` slice from the request. To clear a node's entity type from Go, send `{"labels": []}` directly to `PATCH /graph/node/{uuid}`. ## Updating an Edge Use `graph.edge.update` to update an existing entity edge by UUID. You can change its relationship name, fact, temporal fields, and 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) updated_edge = client.graph.edge.update( uuid_=edge_uuid, name="WORKS_AT", fact="Alice Smith is a principal engineer at Acme Corporation", valid_at="2025-01-15T00:00:00Z", attributes={ "role": "Principal Engineer", "legacy_field": None, # Deletes this attribute }, ) ``` **`TypeScript`** ```typescript TypeScript import { ZepClient } from "@getzep/zep-cloud"; const client = new ZepClient({ apiKey: API_KEY }); const updatedEdge = await client.graph.edge.update(edgeUuid, { name: "WORKS_AT", fact: "Alice Smith is a principal engineer at Acme Corporation", validAt: "2025-01-15T00:00:00Z", attributes: { role: "Principal Engineer", legacy_field: null, // Deletes this attribute }, }); ``` **`Go`** ```go Go import ( "context" "log" zepclient "github.com/getzep/zep-go/v3/client" graph "github.com/getzep/zep-go/v3/graph" "github.com/getzep/zep-go/v3/option" ) client := zepclient.NewClient(option.WithAPIKey(apiKey)) name := "WORKS_AT" fact := "Alice Smith is a principal engineer at Acme Corporation" validAt := "2025-01-15T00:00:00Z" updatedEdge, err := client.Graph.Edge.Update( context.TODO(), edgeUUID, &graph.UpdateEdgeRequest{ Name: &name, Fact: &fact, ValidAt: &validAt, Attributes: map[string]interface{}{ "role": "Principal Engineer", "legacy_field": nil, // Deletes this attribute }, }, ) if err != nil { log.Fatalf("Failed to update edge: %v", err) } log.Printf("Updated edge: %s", updatedEdge.UUID) ``` Edge attributes are merged with the existing attributes. Set an attribute to `null` to delete it. Temporal fields use RFC 3339 / ISO 8601 timestamps. ## Adding a Fact Triplet You can add manually specified fact/node triplets to the graph. Provide the fact, a relationship name, and the source and target node names or UUIDs. Zep will then create a new corresponding edge and nodes, or use existing nodes and an edge if they represent the same entities and relationship. If the new fact invalidates an existing fact, Zep marks the existing fact as invalid and adds the new fact triplet. The `add_fact_triple` method returns a `task_id` 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 client = Zep( api_key=API_KEY, ) result = client.graph.add_fact_triple( user_id=user_id, # Optional: You can use graph_id instead of user_id fact="Paul met Eric", fact_name="MET", target_node_name="Eric Clapton", source_node_name="Paul", ) # The result includes a task_id for tracking processing status task_id = result.task_id print(f"Fact triple task ID: {task_id}") ``` **`TypeScript`** ```typescript TypeScript import { ZepClient } from "@getzep/zep-cloud"; const client = new ZepClient({ apiKey: API_KEY, }); const result = await client.graph.addFactTriple({ userId: userId, // Optional: You can use graphId instead of userId fact: "Paul met Eric", factName: "MET", targetNodeName: "Eric Clapton", sourceNodeName: "Paul", }); // The result includes a task_id for tracking processing status const taskId = result.taskId; console.log(`Fact triple task ID: ${taskId}`); ``` **`Go`** ```go Go import ( "context" "log" "github.com/getzep/zep-go/v3" zepclient "github.com/getzep/zep-go/v3/client" "github.com/getzep/zep-go/v3/option" ) client := zepclient.NewClient( option.WithAPIKey(apiKey), ) userID := "user123" sourceNodeName := "Paul" targetNodeName := "Eric Clapton" result, err := client.Graph.AddFactTriple(context.TODO(), &zep.AddTripleRequest{ UserID: &userID, // Optional: You can use GraphID instead of UserID Fact: "Paul met Eric", FactName: "MET", TargetNodeName: &targetNodeName, SourceNodeName: &sourceNodeName, }) if err != nil { log.Fatalf("Failed to add fact triple: %v", err) } // The result includes a task_id for tracking processing status if result.TaskID == nil { log.Fatal("The add fact triple response did not include a task ID") } log.Printf("Fact triple task ID: %s", *result.TaskID) ``` ### Retrieving Created UUIDs Because fact triple creation is asynchronous, the UUIDs for the edge and nodes are not returned immediately. After the task completes, you can retrieve them by calling `client.task.get()` with the `task_id` returned from `add_fact_triple`. The task's `params` field will contain `edge_uuid`, `source_node_uuid`, and `target_node_uuid`. **`Python`** ```python Python import time from zep_cloud.client import Zep client = Zep(api_key=API_KEY) # Add a fact triple result = client.graph.add_fact_triple( user_id=user_id, fact="Paul met Eric", fact_name="MET", target_node_name="Eric Clapton", source_node_name="Paul", ) task_id = result.task_id if task_id is None: raise RuntimeError("The add fact triple response did not include a task ID") # Poll until the task completes while True: task = client.task.get(task_id=task_id) if task.status == "succeeded": break elif task.status == "failed": raise Exception(f"Task failed: {task.error}") time.sleep(10) # Retrieve the created UUIDs from task.params params = task.params or {} edge_uuid = params.get("edge_uuid") source_node_uuid = params.get("source_node_uuid") target_node_uuid = params.get("target_node_uuid") print(f"Edge UUID: {edge_uuid}") print(f"Source Node UUID: {source_node_uuid}") print(f"Target Node UUID: {target_node_uuid}") ``` **`TypeScript`** ```typescript TypeScript import { ZepClient } from "@getzep/zep-cloud"; const client = new ZepClient({ apiKey: API_KEY }); // Add a fact triple const result = await client.graph.addFactTriple({ userId: userId, fact: "Paul met Eric", factName: "MET", targetNodeName: "Eric Clapton", sourceNodeName: "Paul", }); const taskId = result.taskId; if (!taskId) { throw new Error("The add fact triple response did not include a task ID"); } // Poll until the task completes const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); let task; while (true) { task = await client.task.get(taskId); if (task.status === "succeeded") { break; } else if (task.status === "failed") { throw new Error(`Task failed: ${task.error?.message ?? "Unknown error"}`); } await sleep(10000); } // Retrieve the created UUIDs from task.params const edgeUuid = task.params?.["edge_uuid"]; const sourceNodeUuid = task.params?.["source_node_uuid"]; const targetNodeUuid = task.params?.["target_node_uuid"]; console.log(`Edge UUID: ${edgeUuid}`); console.log(`Source Node UUID: ${sourceNodeUuid}`); console.log(`Target Node UUID: ${targetNodeUuid}`); ``` **`Go`** ```go Go import ( "context" "fmt" "log" "time" "github.com/getzep/zep-go/v3" zepclient "github.com/getzep/zep-go/v3/client" "github.com/getzep/zep-go/v3/option" ) client := zepclient.NewClient( option.WithAPIKey(apiKey), ) userID := "user123" sourceNodeName := "Paul" targetNodeName := "Eric Clapton" // Add a fact triple result, err := client.Graph.AddFactTriple(context.TODO(), &zep.AddTripleRequest{ UserID: &userID, Fact: "Paul met Eric", FactName: "MET", TargetNodeName: &targetNodeName, SourceNodeName: &sourceNodeName, }) if err != nil { log.Fatalf("Failed to add fact triple: %v", err) } if result.TaskID == nil { log.Fatal("The add fact triple response did not include a task ID") } taskID := *result.TaskID // Poll until the task completes var task *zep.GetTaskResponse for { task, err = client.Task.Get(context.TODO(), taskID) if err != nil { log.Fatalf("Failed to get task: %v", err) } if task.Status != nil && *task.Status == "succeeded" { break } else if task.Status != nil && *task.Status == "failed" { log.Fatalf("Task failed: %v", task.Error) } time.Sleep(10 * time.Second) } // Retrieve the created UUIDs from task.Params edgeUUID := task.Params["edge_uuid"] sourceNodeUUID := task.Params["source_node_uuid"] targetNodeUUID := task.Params["target_node_uuid"] fmt.Printf("Edge UUID: %v\n", edgeUUID) fmt.Printf("Source Node UUID: %v\n", sourceNodeUUID) fmt.Printf("Target Node UUID: %v\n", targetNodeUUID) ``` ### 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` / `edge_attributes` — custom scalar properties on those nodes and edges, 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 — no error | | `fact_name` not in ontology | All `edge_attributes` pass through — 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 returns HTTP 400. **`Python`** ```python Python from zep_cloud.client import Zep 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.add_fact_triple( user_id=user_id, fact="Alice works at Acme Corp", fact_name="WORKS_AT", # matches edge type in ontology source_node_name="Alice", source_node_labels=["Person"], # matches entity type "Person" in ontology source_node_attributes={ "age": 30, # validated: must be int "role": "Engineer", # validated: must be text }, target_node_name="Acme Corp", target_node_labels=["Organization"], # not in ontology — passes through freely edge_attributes={ "start_year": 2021, # validated against WORKS_AT schema "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.addFactTriple({ userId: userId, fact: "Alice works at Acme Corp", factName: "WORKS_AT", // matches edge type in ontology sourceNodeName: "Alice", sourceNodeLabels: ["Person"], // matches entity type "Person" in ontology sourceNodeAttributes: { age: 30, // validated: must be int role: "Engineer", // validated: must be text }, targetNodeName: "Acme Corp", targetNodeLabels: ["Organization"], // not in ontology — passes through freely edgeAttributes: { start_year: 2021, // validated against WORKS_AT schema department: "Engineering", }, }); ``` **`Go`** ```go Go import ( "context" "log" "github.com/getzep/zep-go/v3" zepclient "github.com/getzep/zep-go/v3/client" "github.com/getzep/zep-go/v3/option" ) client := zepclient.NewClient( option.WithAPIKey(apiKey), ) userID := "user123" sourceNodeName := "Alice" targetNodeName := "Acme Corp" // 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.AddFactTriple(context.TODO(), &zep.AddTripleRequest{ UserID: &userID, Fact: "Alice works at Acme Corp", FactName: "WORKS_AT", // matches edge type in ontology SourceNodeName: &sourceNodeName, SourceNodeLabels: []string{"Person"}, // matches entity type "Person" in ontology SourceNodeAttributes: map[string]interface{}{ "age": 30, // validated: must be int "role": "Engineer", // validated: must be text }, TargetNodeName: &targetNodeName, TargetNodeLabels: []string{"Organization"}, // not in ontology — passes through freely EdgeAttributes: map[string]interface{}{ "start_year": 2021, // validated against WORKS_AT schema "department": "Engineering", }, }) if err != nil { log.Fatalf("Failed to add fact triple: %v", err) } if result.TaskID == nil { log.Fatal("The add fact triple response did not include a task ID") } log.Printf("Fact triple task ID: %s", *result.TaskID) ``` > **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, which makes the resulting edge and nodes filterable via [episode metadata filters](/searching-the-graph#episode-metadata-filtering) in graph search. 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 from zep_cloud.client import Zep client = Zep(api_key=API_KEY) result = client.graph.add_fact_triple( user_id=user_id, fact="Alice works at Acme Corp", fact_name="WORKS_AT", source_node_name="Alice", target_node_name="Acme Corp", metadata={"source": "crm_import", "confidence": 0.95}, ) ``` **`TypeScript`** ```typescript TypeScript import { ZepClient } from "@getzep/zep-cloud"; const client = new ZepClient({ apiKey: API_KEY }); const result = await client.graph.addFactTriple({ userId: userId, fact: "Alice works at Acme Corp", factName: "WORKS_AT", sourceNodeName: "Alice", targetNodeName: "Acme Corp", metadata: { source: "crm_import", confidence: 0.95 }, }); ``` **`Go`** ```go Go import ( "context" "log" "github.com/getzep/zep-go/v3" zepclient "github.com/getzep/zep-go/v3/client" "github.com/getzep/zep-go/v3/option" ) client := zepclient.NewClient( option.WithAPIKey(apiKey), ) userID := "user123" sourceNodeName := "Alice" targetNodeName := "Acme Corp" result, err := client.Graph.AddFactTriple(context.TODO(), &zep.AddTripleRequest{ UserID: &userID, Fact: "Alice works at Acme Corp", FactName: "WORKS_AT", SourceNodeName: &sourceNodeName, TargetNodeName: &targetNodeName, Metadata: map[string]interface{}{ "source": "crm_import", "confidence": 0.95, }, }) if err != nil { log.Fatalf("Failed to add fact triple: %v", err) } if result.TaskID == nil { log.Fatal("The add fact triple response did not include a task ID") } log.Printf("Fact triple task ID: %s", *result.TaskID) ``` ### 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 provided but node doesn't exist** | Returns a 404 error. You must omit the UUID to create a new node. | | **UUID omitted** | Searches for an existing node by name. If no match is found, creates a new node with an auto-generated UUID. | See the [associated SDK reference](/sdk-reference/graph/add-fact-triple) for complete details on all available parameters. ### 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 | | `created_at`, `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