Back to all resources
ContX IQ ContX IQ Json

ContX IQ: Upsert Relationships Between Existing Nodes

Create or update (upsert) relationships between existing nodes in the graph. This example demonstrates using the allowed_upserts.relationships policy directive to authorize ACCEPTED relationships between Person and Contract nodes.

ContX IQ: Upsert Relationships Between Existing Nodes

This example demonstrates relationship upsert operations:

Key concept: "Upsert" means "insert or update" - if the relationship exists, it's updated; if not, it's created.

Policy configuration:

- Uses allowed_upserts.relationships to specify which relationship types can be created/updated

- In this example: ACCEPTED relationships between Person and Contract nodes

Workflow:

1. Link Application to Company (prerequisite for authorization)

2. Create policy with allowed_upserts.relationships directive

3. Create and execute query that upserts Person -[ACCEPTED]-> Contract relationships

Difference from ciq-4: This example focuses on upserting relationships only (no new nodes created).

Use case

Scenario: Your application needs to record when a person accepts a contract.

Operation: Create an ACCEPTED relationship from Person to Contract.

Upsert behavior:

- If Person -[ACCEPTED]-> Contract doesn't exist: Create new relationship

- If it already exists: Update relationship properties (if any)

Authorization check: The ContX IQ engine verifies that:

1. The requesting Application is authorized (via $_appId filter)

2. The relationship type (ACCEPTED) is in the allowed_upserts.relationships list

3. The source node (Person) and target node (Contract) match the policy

Graph change:

Before: Person(Ryan) and Contract(Contract1) exist without connection

After: Person(Ryan) -[ACCEPTED]-> Contract(Contract1)

ikg

Requirements

Prerequisites:

- ServiceAccount credentials: For creating policies and queries (Bearer token)

- AppAgent credentials: For data ingestion and query execution (X-IK-ClientKey)

Required API access:

- POST /capture/v1/nodes/ and /capture/v1/relationships/ (initial data)

- POST /configs/v1/authorization-policies (create policies)

- POST /configs/v1/knowledge-queries (create queries)

- POST /contx-iq/v1/execute (run queries)

Steps

Step 1: Ingest Initial Graph Data

- Authentication: AppAgent credential (X-IK-ClientKey header)

- Action: POST nodes (Person, Contract, Vehicle, Company) and initial relationships

- Result: Graph ready for relationship upsert

Step 2: Create Application Linking Policy

- Authentication: ServiceAccount credential (Bearer token)

- Action: POST policy allowing _Application to create HAS_AGREEMENT_WITH relationships

- Result: Policy ID returned

Step 3: Create Application Linking Query

- Authentication: ServiceAccount credential (Bearer token)

- Action: POST write query for Application -> Company linking

- Result: Query ID returned

Step 4: Execute Application Linking

- Authentication: AppAgent credential (X-IK-ClientKey header)

- Action: Execute the linking query

- Result: _Application connected to Company in graph

Step 5: Create Relationship Upsert Policy

- Authentication: ServiceAccount credential (Bearer token)

- Action: POST policy with allowed_upserts.relationships containing:

- source: Person

- target: Contract

- type: ACCEPTED

- Result: Policy ID returned

Step 6: Create Relationship Upsert Query

- Authentication: ServiceAccount credential (Bearer token)

- Action: POST query that upserts ACCEPTED relationships

- Result: Query ID returned

Step 7: Execute Relationship Upsert

- Authentication: AppAgent credential (X-IK-ClientKey header)

- Action: Execute the upsert query

- Result: Person -[ACCEPTED]-> Contract relationship created/updated

Step 8: Cleanup

- Action: DELETE queries and policies

Step 1a

Capture nodes: Person, Contract, Vehicle, Company nodes that will be connected by the upserted relationship.

POST https://eu.api.indykite.com/capture/v1/nodes/Json
{
  "nodes": [
    {
      "external_id": "alice",
      "is_identity": true,
      "type": "Person",
      "properties": [
        {
          "type": "email",
          "value": "alice@email.com"
        },
        {
          "type": "given_name",
          "value": "Alice"
        },
        {
          "type": "last_name",
          "value": "Smith"
        }
      ]
    },
    {
      "external_id": "ryan",
      "is_identity": true,
      "type": "Person",
      "properties": [
        {
          "type": "email",
          "value": "ryan@yahoo.co.uk"
        },
        {
          "type": "given_name",
          "value": "ryan"
        },
        {
          "type": "last_name",
          "value": "mushu"
        }
      ]
    },
    {
      "external_id": "tilda",
      "is_identity": true,
      "type": "Person",
      "properties": [
        {
          "type": "email",
          "value": "tilda@yahoo.co.uk"
        },
        {
          "type": "given_name",
          "value": "tilda"
        },
        {
          "type": "last_name",
          "value": "mushu"
        }
      ]
    },
    {
      "external_id": "cb123",
      "type": "PaymentMethod",
      "properties": [
        {
          "type": "payment_name",
          "value": "Credit Card"
        }
      ]
    },
    {
      "external_id": "kl123",
      "type": "PaymentMethod",
      "properties": [
        {
          "type": "payment_name",
          "value": "Klarna"
        }
      ]
    },
    {
      "external_id": "ct123",
      "type": "Contract",
      "properties": [
        {
          "type": "category",
          "value": "Insurance"
        },
        {
          "type": "status",
          "value": "Active"
        },
        {
          "type": "number",
          "value": "hfgrten123",
          "metadata": {
            "assurance_level": 3,
            "source": "BRREG"
          }
        }
      ]
    },
    {
      "external_id": "ct234",
      "type": "Contract",
      "properties": [
        {
          "type": "category",
          "value": "Insurance"
        },
        {
          "type": "status",
          "value": "Active"
        },
        {
          "type": "number",
          "value": "hfgrten234",
          "metadata": {
            "assurance_level": 3,
            "source": "BRREG"
          }
        }
      ]
    },
    {
      "external_id": "ct985",
      "type": "Contract",
      "properties": [
        {
          "type": "category",
          "value": "Insurance"
        },
        {
          "type": "status",
          "value": "Active"
        },
        {
          "type": "number",
          "value": "hfgrten985",
          "metadata": {
            "assurance_level": 3,
            "source": "BRREG"
          }
        }
      ]
    },
    {
      "external_id": "car1",
      "type": "Vehicle",
      "properties": [
        {
          "type": "category",
          "value": "Car"
        },
        {
          "type": "is_active",
          "value": true
        },
        {
          "type": "vin",
          "value": "rtfhcnvjt471"
        }
      ]
    },
    {
      "external_id": "car2",
      "type": "Vehicle",
      "properties": [
        {
          "type": "category",
          "value": "Car"
        },
        {
          "type": "is_active",
          "value": true
        },
        {
          "type": "vin",
          "value": "kdcbfrt178"
        }
      ]
    },
    {
      "external_id": "truck1",
      "type": "Vehicle",
      "properties": [
        {
          "type": "category",
          "value": "Truck"
        },
        {
          "type": "is_active",
          "value": true
        },
        {
          "type": "vin",
          "value": "sncnrkcldp"
        }
      ]
    },
    {
      "external_id": "license1",
      "type": "LicenseNumber",
      "properties": [
        {
          "type": "status",
          "value": "Active"
        },
        {
          "type": "number",
          "value": "AX123456",
          "metadata": {
            "assurance_level": 3,
            "source": "BRREG"
          }
        }
      ]
    },
    {
      "external_id": "license2",
      "type": "LicenseNumber",
      "properties": [
        {
          "type": "status",
          "value": "Active"
        },
        {
          "type": "number",
          "value": "OL123456",
          "metadata": {
            "assurance_level": 3,
            "source": "BRREG"
          }
        }
      ]
    },
    {
      "external_id": "license3",
      "type": "LicenseNumber",
      "properties": [
        {
          "type": "status",
          "value": "Active"
        },
        {
          "type": "number",
          "value": "VN123456",
          "metadata": {
            "assurance_level": 3,
            "source": "BRREG"
          }
        }
      ]
    },
    {
      "external_id": "company1",
      "type": "Company",
      "properties": [
        {
          "type": "name",
          "value": "Company1"
        },
        {
          "type": "registration",
          "value": "256314523"
        }
      ]
    },
    {
      "external_id": "company2",
      "type": "Company",
      "properties": [
        {
          "type": "name",
          "value": "Company2"
        },
        {
          "type": "registration",
          "value": "942365123"
        }
      ]
    },
    {
      "external_id": "application1",
      "type": "Application",
      "properties": [
        {
          "type": "name",
          "value": "Application"
        }
      ]
    },
    {
      "external_id": "application2",
      "type": "Application",
      "properties": [
        {
          "type": "name",
          "value": "Application2"
        }
      ]
    }
  ]
}

Step 1b

Capture initial relationships (not including ACCEPTED - that will be upserted).

POST https://eu.api.indykite.com/capture/v1/relationships/Json
{
  "relationships": [
    {
      "source": {
        "external_id": "ryan",
        "type": "Person"
      },
      "target": {
        "external_id": "cb123",
        "type": "PaymentMethod"
      },
      "type": "HAS"
    },
    {
      "source": {
        "external_id": "tilda",
        "type": "Person"
      },
      "target": {
        "external_id": "kl123",
        "type": "PaymentMethod"
      },
      "type": "HAS"
    },
    {
      "source": {
        "external_id": "alice",
        "type": "Person"
      },
      "target": {
        "external_id": "cb123",
        "type": "PaymentMethod"
      },
      "type": "HAS"
    },
    {
      "source": {
        "external_id": "ryan",
        "type": "Person"
      },
      "target": {
        "external_id": "ct123",
        "type": "Contract"
      },
      "type": "ACCEPTED"
    },
    {
      "source": {
        "external_id": "tilda",
        "type": "Person"
      },
      "target": {
        "external_id": "ct234",
        "type": "Contract"
      },
      "type": "ACCEPTED"
    },
    {
      "source": {
        "external_id": "alice",
        "type": "Person"
      },
      "target": {
        "external_id": "ct985",
        "type": "Contract"
      },
      "type": "ACCEPTED"
    },
    {
      "source": {
        "external_id": "ct123",
        "type": "Contract"
      },
      "target": {
        "external_id": "car1",
        "type": "Vehicle"
      },
      "type": "COVERS"
    },
    {
      "source": {
        "external_id": "ct985",
        "type": "Contract"
      },
      "target": {
        "external_id": "car1",
        "type": "Vehicle"
      },
      "type": "COVERS"
    },
    {
      "source": {
        "external_id": "ct234",
        "type": "Contract"
      },
      "target": {
        "external_id": "truck1",
        "type": "Vehicle"
      },
      "type": "COVERS"
    },
    {
      "source": {
        "external_id": "car1",
        "type": "Vehicle"
      },
      "target": {
        "external_id": "license1",
        "type": "LicenseNumber"
      },
      "type": "HAS"
    },
    {
      "source": {
        "external_id": "truck1",
        "type": "Vehicle"
      },
      "target": {
        "external_id": "license2",
        "type": "LicenseNumber"
      },
      "type": "HAS"
    },
    {
      "source": {
        "external_id": "car2",
        "type": "Vehicle"
      },
      "target": {
        "external_id": "license3",
        "type": "LicenseNumber"
      },
      "type": "HAS"
    },
    {
      "source": {
        "external_id": "company1",
        "type": "Company"
      },
      "target": {
        "external_id": "car1",
        "type": "Vehicle"
      },
      "type": "OWNS"
    },
    {
      "source": {
        "external_id": "company1",
        "type": "Company"
      },
      "target": {
        "external_id": "car2",
        "type": "Vehicle"
      },
      "type": "OWNS"
    },
    {
      "source": {
        "external_id": "company1",
        "type": "Company"
      },
      "target": {
        "external_id": "truck1",
        "type": "Vehicle"
      },
      "type": "OWNS"
    },
    {
      "source": {
        "external_id": "application1",
        "type": "Application"
      },
      "target": {
        "external_id": "company1",
        "type": "Company"
      },
      "type": "HAS_AGREEMENT_WITH"
    },
    {
      "source": {
        "external_id": "application2",
        "type": "Application"
      },
      "target": {
        "external_id": "company1",
        "type": "Company"
      },
      "type": "HAS_AGREEMENT_WITH"
    }
  ]
}

Step 2a

Policy JSON allowing _Application to create HAS_AGREEMENT_WITH relationships (prerequisite for authorization).

policy.jsonJson
{
  "meta": {
    "policy_version": "1.0-ciq"
  },
  "subject": {
    "type": "_Application"
  },
  "condition": {
    "cypher": "MATCH (subject:_Application) MATCH (company:Company)-[r2:OWNS]->(vehicle:Vehicle)",
    "filter": [
      {
        "operator": "AND",
        "operands": [
          {
            "attribute": "subject.external_id",
            "operator": "=",
            "value": "$_appId"
          },
          {
            "attribute": "company.external_id",
            "operator": "=",
            "value": "$companyID"
          }
        ]
      }
    ]
  },
  "allowed_upserts": {
    "relationships": {
      "relationship_types": [
        {
          "type": "HAS_AGREEMENT_WITH",
          "source_node_label": "_Application",
          "target_node_label": "Company"
        }
      ]
    }
  },
  "allowed_reads": {
    "nodes": [
      "company.*",
      "subject.*"
    ]
  }
}

Step 2b

POST request to create the application linking policy.

POST https://eu.api.indykite.com/configs/v1/authorization-policiesJson
{
  "project_id": "your_project_gid",
  "description": "description of policy",
  "display_name": "policy name",
  "name": "policy-name",
  "policy": "{\"meta\":{\"policy_version\":\"1.0-ciq\"},\"subject\":{\"type\":\"_Application\"},\"condition\":{\"cypher\":\"MATCH (subject:_Application) MATCH (company:Company)-[r2:OWNS]->(vehicle:Vehicle)\",\"filter\":[{\"operator\":\"AND\",\"operands\":[{\"attribute\":\"subject.external_id\",\"operator\":\"=\",\"value\":\"$_appId\"},{\"attribute\":\"company.external_id\",\"operator\":\"=\",\"value\":\"$companyID\"}]}]},\"allowed_upserts\":{\"relationships\":{\"relationship_types\":[{\"type\":\"HAS_AGREEMENT_WITH\",\"source_node_label\":\"_Application\",\"target_node_label\":\"Company\"}]}},\"allowed_reads\":{\"nodes\":[\"company.*\",\"subject.*\"]}}",
  "status": "ACTIVE",
  "tags": []
}

Step 2c

GET request to verify the policy was created.

GET https://eu.api.indykite.com/configs/v1/authorization-policies/{policy_id}Json
{
  "id": "your_policy_configuration_gid"
}

Step 3a

Write query JSON for Application -> Company linking.

knowledge_query.jsonJson
{
  "nodes": [
    "subject.external_id"
  ],
  "relationships": [
    "r1"
  ],
  "upsert_relationships": [
    {
      "name": "r1",
      "source": "subject",
      "target": "company",
      "type": "HAS_AGREEMENT_WITH"
    }
  ]
}

Step 3b

POST request to create the linking query.

POST https://eu.api.indykite.com/configs/v1/knowledge-queriesJson
{
  "project_id": "your_project_gid",
  "description": "description of knowledge query",
  "display_name": "knowledge query name",
  "name": "knowledge-query-name",
  "policy_id": "your_policy_gid",
  "query": "{\"nodes\":[\"subject.external_id\"],\"relationships\":[\"r1\"],\"upsert_relationships\":[{\"name\":\"r1\",\"source\":\"subject\",\"target\":\"company\",\"type\":\"HAS_AGREEMENT_WITH\"}]}",
  "status": "ACTIVE"
}

Step 3c

GET request to verify the query was created.

GET https://eu.api.indykite.com/configs/v1/knowledge-queries/{query_id}Json
{
  "id": "your_knowledge_query_configuration_gid"
}

Step 4a

Execute the linking query to connect Application to Company.

POST https://eu.api.indykite.com/contx-iq/v1/executeJson
{
  "id": "ciq_query_gid",
  "input_params": {
    "companyID": "company1"
  }
}

Step 4b

Response confirming the Application -> Company link was created.

response.jsonJson
{
  "data": [
    {
      "nodes": {
        "subject.external_id": "application_external_id"
      },
      "relationships": {
        "r1": {
          "Id": 1152932499723124700,
          "ElementId": "5:3a2b09d5-2923-45d7-8453-8b1c698427b0:1152932499723124736",
          "StartId": 0,
          "StartElementId": "4:3a2b09d5-2923-45d7-8453-8b1c698427b0:0",
          "EndId": 15,
          "EndElementId": "4:3a2b09d5-2923-45d7-8453-8b1c698427b0:15",
          "Type": "HAS_AGREEMENT_WITH",
          "Props": {
            "create_time": "2025-06-09T15:12:46.374Z",
            "id": "48BJHS2CTFKcVpD4cUF8IA",
            "update_time": "2025-06-09T15:12:46.374Z"
          }
        }
      }
    },
    {
      "nodes": {
        "subject.external_id": "application_external_id"
      },
      "relationships": {
        "r1": {
          "Id": 1152932499723124700,
          "ElementId": "5:3a2b09d5-2923-45d7-8453-8b1c698427b0:1152932499723124736",
          "StartId": 0,
          "StartElementId": "4:3a2b09d5-2923-45d7-8453-8b1c698427b0:0",
          "EndId": 15,
          "EndElementId": "4:3a2b09d5-2923-45d7-8453-8b1c698427b0:15",
          "Type": "HAS_AGREEMENT_WITH",
          "Props": {
            "create_time": "2025-06-09T15:12:46.374Z",
            "id": "48BJHS2CTFKcVpD4cUF8IA",
            "update_time": "2025-06-09T15:12:46.374Z"
          }
        }
      }
    },
    {
      "nodes": {
        "subject.external_id": "application_external_id"
      },
      "relationships": {
        "r1": {
          "Id": 1152932499723124700,
          "ElementId": "5:3a2b09d5-2923-45d7-8453-8b1c698427b0:1152932499723124736",
          "StartId": 0,
          "StartElementId": "4:3a2b09d5-2923-45d7-8453-8b1c698427b0:0",
          "EndId": 15,
          "EndElementId": "4:3a2b09d5-2923-45d7-8453-8b1c698427b0:15",
          "Type": "HAS_AGREEMENT_WITH",
          "Props": {
            "create_time": "2025-06-09T15:12:46.374Z",
            "id": "48BJHS2CTFKcVpD4cUF8IA",
            "update_time": "2025-06-09T15:12:46.374Z"
          }
        }
      }
    }
  ]
}

Step 5a

Policy JSON with allowed_upserts.relationships directive. Specifies that ACCEPTED relationships can be created between Person (source) and Contract (target) nodes.

policy.jsonJson
{
  "meta": {
    "policy_version": "1.0-ciq"
  },
  "subject": {
    "type": "_Application"
  },
  "condition": {
    "cypher": "MATCH (subject:_Application)-[r1:HAS_AGREEMENT_WITH]->(company:Company)-[r2:OWNS]->(vehicle:Vehicle)<-[r3:COVERS]-(contract:Contract) MATCH (person:Person)",
    "filter": [
      {
        "operator": "AND",
        "operands": [
          {
            "attribute": "subject.external_id",
            "operator": "=",
            "value": "$_appId"
          },
          {
            "attribute": "contract.external_id",
            "operator": "=",
            "value": "$contractID"
          },
          {
            "attribute": "person.external_id",
            "operator": "=",
            "value": "$personID"
          }
        ]
      }
    ]
  },
  "allowed_upserts": {
    "relationships": {
      "relationship_types": [
        {
          "type": "ACCEPTED",
          "source_node_label": "Person",
          "target_node_label": "Contract"
        }
      ]
    }
  },
  "allowed_reads": {
    "nodes": [
      "person.*"
    ]
  }
}

Step 5b

POST request to create the relationship upsert policy.

POST https://eu.api.indykite.com/configs/v1/authorization-policiesJson
{
  "project_id": "your_project_gid",
  "description": "description of policy",
  "display_name": "policy name",
  "name": "policy-name",
  "policy": "{\"meta\":{\"policy_version\":\"1.0-ciq\"},\"subject\":{\"type\":\"_Application\"},\"condition\":{\"cypher\":\"MATCH (subject:_Application)-[r1:HAS_AGREEMENT_WITH]->(company:Company)-[r2:OWNS]->(vehicle:Vehicle)<-[r3:COVERS]-(contract:Contract) MATCH (person:Person)\",\"filter\":[{\"operator\":\"AND\",\"operands\":[{\"attribute\":\"subject.external_id\",\"operator\":\"=\",\"value\":\"$_appId\"},{\"attribute\":\"contract.external_id\",\"operator\":\"=\",\"value\":\"$contractID\"},{\"attribute\":\"person.external_id\",\"operator\":\"=\",\"value\":\"$personID\"}]}]},\"allowed_upserts\":{\"relationships\":{\"relationship_types\":[{\"type\":\"ACCEPTED\",\"source_node_label\":\"Person\",\"target_node_label\":\"Contract\"}]}},\"allowed_reads\":{\"nodes\":[\"person.*\"]}}",
  "status": "ACTIVE",
  "tags": []
}

Step 5c

GET request to verify the policy was created.

GET https://eu.api.indykite.com/configs/v1/authorization-policies/{policy_id}Json
{
  "id": "your_policy_configuration_gid"
}

Step 6a

Query JSON that upserts ACCEPTED relationships. The query matches Person and Contract nodes, then creates/updates the ACCEPTED relationship between them.

knowledge_query.jsonJson
{
  "nodes": [
    "person.external_id"
  ],
  "relationships": [
    "r4"
  ],
  "upsert_relationships": [
    {
      "name": "r4",
      "source": "person",
      "target": "contract",
      "type": "ACCEPTED"
    }
  ]
}

Step 6b

POST request to create the upsert query.

POST https://eu.api.indykite.com/configs/v1/knowledge-queriesJson
{
  "project_id": "your_project_gid",
  "description": "description of knowledge query",
  "display_name": "knowledge query name",
  "name": "knowledge-query-name",
  "policy_id": "your_policy_gid",
  "query": "{\"nodes\":[\"person.external_id\"],\"relationships\":[\"r4\"],\"upsert_relationships\":[{\"name\":\"r4\",\"source\":\"person\",\"target\":\"contract\",\"type\":\"ACCEPTED\"}]}",
  "status": "ACTIVE"
}

Step 6c

GET request to verify the query was created.

GET https://eu.api.indykite.com/configs/v1/knowledge-queries/{query_id}Json
{
  "id": "your_knowledge_query_configuration_gid"
}

Step 7a

Execute the upsert query. Creates Person -[ACCEPTED]-> Contract relationship.

POST https://eu.api.indykite.com/contx-iq/v1/executeJson
{
  "id": "knowledge_query_gid",
  "input_params": {
    "contractID": "ct123",
    "personID": "tilda"
  }
}

Step 7b

Response confirming the ACCEPTED relationship was upserted.

response.jsonJson
{
  "data": [
    {
      "nodes": {
        "person.external_id": "tilda"
      },
      "relationships": {
        "r4": {
          "Id": 1155177702467043300,
          "ElementId": "5:3a2b09d5-2923-45d7-8453-8b1c698427b0:1155177702467043332",
          "StartId": 4,
          "StartElementId": "4:3a2b09d5-2923-45d7-8453-8b1c698427b0:4",
          "EndId": 8,
          "EndElementId": "4:3a2b09d5-2923-45d7-8453-8b1c698427b0:8",
          "Type": "ACCEPTED",
          "Props": {
            "create_time": "2025-06-09T10:15:23.494Z",
            "id": "ioSB2XEIRQCjd5hdxkf48w",
            "update_time": "2025-06-09T10:15:23.494Z"
          }
        }
      }
    }
  ]
}

Step 8a

DELETE request to remove the queries.

DELETE https://eu.api.indykite.com/configs/v1/knowledge-queries/{query_id}Json
{
  "id": "your_knowledge_query_configuration_gid"
}

Step 8b

DELETE request to remove the policies. Note: The upserted relationships remain in the graph.

DELETE https://eu.api.indykite.com/configs/v1/authorization-policies/{policy_id}Json
{
  "id": "your_policy_configuration_gid"
}