Back to all resources
ContX IQContX IQJson

ContX IQ: Create a User Node from an Access Token and Authorize Through Organization Membership

Create a User node from the caller's access token, link it to an ingested UserProfile, then read and upsert Event nodes according to the UserProfile's BELONGS_TO relationship with its Organization. The policy filter reads the token's sub claim and the role property stored on the relationship.

ContX IQ: Create a User Node from an Access Token and Authorize Through Organization Membership

This example shows how a ContX IQ query can create graph data from the caller's access token, and how a later policy can authorize through the relationships that data is part of:

Key concepts:

1. A Knowledge Query can upsert a node whose external_id is the token's sub claim ($token.sub), so the caller becomes a User node in the graph without a separate ingest.

2. The User node is linked to an ingested UserProfile, which is the policy subject.

3. The UserProfile has a BELONGS_TO relationship with an Organization; the relationship carries a role property (Member or Admin).

4. Later policies match the token to the User node (user.external_id = $token.sub) and filter on the relationship property (bt.role).

Workflow:

1. Ingest UserProfile, Organization and Event nodes, with BELONGS_TO and PART_OF relationships

2. Execute a query with each user's token to create the User node and the HAS relationship from the UserProfile

3. Read the Events of the Organization the UserProfile belongs to (relationship role Member)

4. Upsert a new Event into that Organization (relationship role Admin)

The authorization comes from the graph: who the token maps to, which Organization the profile belongs to, and what the relationship says about that membership.

Use case

Scenario: An event platform where every user belongs to an organization, and what a user may do with the organization's events is written on the membership relationship.

Ingested graph:

- UserProfile(alice) -[BELONGS_TO {role: "Member"}]-> Organization(org1)

- UserProfile(bob) -[BELONGS_TO {role: "Admin"}]-> Organization(org1)

- Event(event_lambda) -[PART_OF]-> Organization(org1)

Linking the tokens:

- Execute with alice's token -> User(<alice token sub>) is created and UserProfile(alice) -[HAS]-> User

- Execute with bob's token -> User(<bob token sub>) is created and UserProfile(bob) -[HAS]-> User

Results:

- Read query with alice's token: the policy requires bt.role = "Member" on the BELONGS_TO relationship -> event_lambda is returned.

- Upsert query with bob's token: the policy requires bt.role = "Admin" -> Event(event14) is created and linked to org1 with PART_OF.

ikg

Requirements

Prerequisites:

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

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

- User access tokens: one token for alice and one for bob, issued by your IdP

- Token Introspect configuration: validates those tokens on /contx-iq/v1/execute

How user tokens are used:

- Pass the user token in the Authorization header: "Bearer {user_access_token}"

- The policy and query read the token's sub claim as $token.sub

- The UserProfile is the policy subject; the User node created from the token is matched to it in the policy cypher

Steps

Step 1: Ingest Profiles, Organization and Event

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

- Action: POST nodes: UserProfile (alice, bob), Organization (org1), Event (event_lambda)

- Action: POST relationships: BELONGS_TO (UserProfile -> Organization, with a role property), PART_OF (Event -> Organization)

- Result: Graph ready

Step 2: Create the User Creation Policy

- Authentication: ServiceAccount credential (Bearer token)

- Action: POST policy with subject type UserProfile, filter subject.external_id = $subject_external_id, allowing:

- READ on the subject node

- UPSERT User nodes

- UPSERT HAS relationships (UserProfile -> User)

- Result: Policy ID returned

Step 3: Create the User Creation Query

- Authentication: ServiceAccount credential (Bearer token)

- Action: POST query that upserts a User node with external_id $token.sub and the DigitalTwin label, sets its email from $email, and upserts the HAS relationship from the subject

- Result: Query ID returned

Step 4: Execute with alice's Token

- Authentication: AppAgent credential + alice's user token (Bearer header)

- Action: POST to /contx-iq/v1/execute with input_params subject_external_id = alice and email

- Result: User node created from the token and linked to UserProfile(alice)

Step 5: Execute with bob's Token

- Authentication: AppAgent credential + bob's user token (Bearer header)

- Action: POST to /contx-iq/v1/execute with input_params subject_external_id = bob and email

- Result: User node created from the token and linked to UserProfile(bob)

Step 6: Create the Event Read Policy

- Authentication: ServiceAccount credential (Bearer token)

- Action: POST policy with subject type UserProfile and cypher

(user:User)<-[:HAS]-(subject:UserProfile)-[bt:BELONGS_TO]->(org:Organization)<-[:PART_OF]-(event:Event)

- Filter: subject.external_id = $subject_external_id AND user.external_id = $token.sub AND bt.role = "Member"

- Allowed reads: event, event.*, org.external_id, subject.property.name

- Result: Policy ID returned

Step 7: Create the Event Read Query

- Authentication: ServiceAccount credential (Bearer token)

- Action: POST query returning event, filtered on subject.property.email = $email

- Result: Query ID returned

Step 8: Execute the Read with alice's Token

- Authentication: AppAgent credential + alice's token

- Action: POST to /contx-iq/v1/execute with input_params subject_external_id = alice and email

- Result: event_lambda returned

Step 9: Create the Event Upsert Policy

- Authentication: ServiceAccount credential (Bearer token)

- Action: POST policy with subject type UserProfile and cypher

(user:User)<-[:HAS]-(subject:UserProfile)-[bt:BELONGS_TO]->(org:Organization)

- Filter: subject.external_id = $profile_external_id AND $token.sub = $user_external_id AND bt.role = "Admin"

- Allowed upserts: Event nodes, PART_OF relationships (Event -> Organization)

- Result: Policy ID returned

Step 10: Create the Event Upsert Query

- Authentication: ServiceAccount credential (Bearer token)

- Action: POST query that upserts an Event with external_id $eventId and properties $title and $link, and the PART_OF relationship to org

- Result: Query ID returned

Step 11: Execute the Upsert with bob's Token

- Authentication: AppAgent credential + bob's token

- Action: POST to /contx-iq/v1/execute with input_params profile_external_id = bob, user_external_id = the token's sub, eventId, title, link

- Result: Event(event14) created and linked to org1

Step 1

Capture the nodes needed for this use case.

POST https://eu.api.indykite.com/capture/v1/nodes/Json
{
  "nodes": [
    {
      "external_id": "alice",
      "type": "UserProfile",
      "properties": [
        {
          "type": "email",
          "value": "alice@email.com"
        }
      ]
    },
    {
      "external_id": "bob",
      "type": "UserProfile",
      "properties": [
        {
          "type": "email",
          "value": "bob@email.com"
        }
      ]
    },
    {
      "external_id": "org1",
      "type": "Organization",
      "properties": [
        {
          "type": "name",
          "value": "Org1"
        }
      ]
    },
    {
      "external_id": "event_lambda",
      "type": "Event",
      "labels": [],
      "is_identity": false,
      "properties": [
        {
          "type": "title",
          "value": "Event Lambda",
          "metadata": {
            "assurance_level": 1,
            "source": "Some Source",
            "verified_time": "2024-04-10T06:28:16Z"
          }
        },
        {
          "type": "startDate",
          "value": "2025-01-01",
          "metadata": {
            "assurance_level": 1,
            "source": "Some Source",
            "verified_time": "2025-04-10T06:28:16Z"
          }
        },
        {
          "type": "link",
          "value": "https://events.com",
          "metadata": {
            "assurance_level": 1,
            "source": "Some Source",
            "verified_time": "2025-04-10T06:28:16Z"
          }
        },
        {
          "type": "description",
          "value": "Description ...",
          "metadata": {
            "assurance_level": 1,
            "source": "Some Source",
            "verified_time": "2025-04-10T06:28:16Z"
          }
        }
      ]
    }
  ]
}

Capture the relationships needed for this use case.

POST https://eu.api.indykite.com/capture/v1/relationships/Json
{
  "relationships": [
    {
      "source": {
        "external_id": "alice",
        "type": "UserProfile"
      },
      "target": {
        "external_id": "org1",
        "type": "Organization"
      },
      "type": "BELONGS_TO",
      "properties": [
        {
          "type": "role",
          "value": "Member"
        }
      ]
    },
    {
      "source": {
        "external_id": "bob",
        "type": "UserProfile"
      },
      "target": {
        "external_id": "org1",
        "type": "Organization"
      },
      "type": "BELONGS_TO",
      "properties": [
        {
          "type": "role",
          "value": "Admin"
        }
      ]
    },
    {
      "source": {
        "external_id": "event_lambda",
        "type": "Event"
      },
      "target": {
        "external_id": "org1",
        "type": "Organization"
      },
      "type": "PART_OF"
    }
  ]
}

Step 2

Create a CIQ Policy which designates the Subject nodes can be read,

the User nodes can be upserted and relationships between User and UserProfile can be upserted.

policy.jsonJson
{
  "meta": {
    "policy_version": "1.0-ciq"
  },
  "subject": {
    "type": "UserProfile"
  },
  "condition": {
    "cypher": "MATCH (subject:UserProfile)",
    "filter": [
      {
        "operator": "=",
        "attribute": "subject.external_id",
        "value": "$subject_external_id"
      }
    ]
  },
  "allowed_reads": {
    "nodes": [
      "subject",
      "subject.*"
    ]
  },
  "allowed_upserts": {
    "nodes": {
      "node_types": [
        "User"
      ]
    },
    "relationships": {
      "relationship_types": [
        {
          "type": "HAS",
          "source_node_label": "UserProfile",
          "target_node_label": "User"
        }
      ]
    }
  }
}

Request to create the CIQ Policy configuration using REST.

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\":\"UserProfile\"},\"condition\":{\"cypher\":\"MATCH (subject:UserProfile)\",\"filter\":[{\"operator\":\"=\",\"attribute\":\"subject.external_id\",\"value\":\"$subject_external_id\"}]},\"allowed_reads\":{\"nodes\":[\"subject\",\"subject.*\"]},\"allowed_upserts\":{\"nodes\":{\"node_types\":[\"User\"]},\"relationships\":{\"relationship_types\":[{\"type\":\"HAS\",\"source_node_label\":\"UserProfile\",\"target_node_label\":\"User\"}]}}}",
  "status": "ACTIVE",
  "tags": []
}

Request to read the CIQ Policy configuration using REST.

policy_request.jsonJson
{
  "id": "your_policy_configuration_gid"
}

Step 3

Create a CIQ Query in the context of the policy to retrieve and upsert the data. The labels: ["DigitalTwin"] entry creates the User as an identity node - the same result as is_identity: true in the Capture API.

knowledge_query.jsonJson
{
  "nodes": [
    "subject",
    "subject.property.email",
    "user.property.email"
  ],
  "relationships": [],
  "upsert_nodes": [
    {
      "name": "user",
      "type": "User",
      "external_id": "$token.sub",
      "labels": [
        "DigitalTwin"
      ],
      "properties": [
        {
          "type": "email",
          "value": "$email"
        }
      ]
    }
  ],
  "upsert_relationships": [
    {
      "name": "has",
      "source": "subject",
      "target": "user",
      "type": "HAS"
    }
  ]
}

Request to create a CIQ Query configuration using REST.

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\",\"subject.property.email\",\"user.property.email\"],\"relationships\":[],\"upsert_nodes\":[{\"name\":\"user\",\"type\":\"User\",\"external_id\":\"$token.sub\",\"labels\":[\"DigitalTwin\"],\"properties\":[{\"type\":\"email\",\"value\":\"$email\"}]}],\"upsert_relationships\":[{\"name\":\"has\",\"source\":\"subject\",\"target\":\"user\",\"type\":\"HAS\"}]}",
  "status": "ACTIVE"
}

Read the CIQ Query Configuration.

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

Step 4

Run a CIQ Execution to create a User node from the first access token and link it to the corresponding UserProfile.

POST https://eu.api.indykite.com/contx-iq/v1/executeJson
{
  "id": "knowledge_query_gid",
  "input_params": {
    "subject_external_id": "alice",
    "user_external_id": "alice_user_external_id",
    "email": "alice@email.com"
  },
  "page_token": 1
}

CIQ Execution response.

response.jsonJson
{
  "data": [
    {
      "nodes": {
        "subject": {
          "Id": 14,
          "ElementId": "4:0f1c76a0-92f1-4474-80af-aa4c317e636a:14",
          "Labels": [
            "Unique",
            "Resource",
            "UserProfile"
          ],
          "Props": {
            "_service": "capture-api",
            "create_time": "2025-06-13T16:08:25.47Z",
            "external_id": "alice",
            "id": "4H1gEySmTGasbkjWDyyuXg",
            "type": "UserProfile",
            "update_time": "2025-06-13T16:08:25.47Z"
          }
        },
        "subject.property.email": "alice@email.com",
        "user.property.email": "alice@email.com"
      }
    }
  ]
}

Step 5

Run a CIQ Execution to create a User node from the second access token and link it to the corresponding UserProfile.

POST https://eu.api.indykite.com/contx-iq/v1/executeJson
{
  "id": "knowledge_query_gid",
  "input_params": {
    "subject_external_id": "bob",
    "user_external_id": "bob_user_external_id",
    "email": "bob@email.com"
  },
  "page_token": 1
}

CIQ Execution response.

response.jsonJson
{
  "data": [
    {
      "nodes": {
        "subject": {
          "Id": 15,
          "ElementId": "4:0f1c76a0-92f1-4474-80af-aa4c317e636a:15",
          "Labels": [
            "Unique",
            "Resource",
            "UserProfile"
          ],
          "Props": {
            "_service": "capture-api",
            "create_time": "2025-06-13T16:08:25.47Z",
            "external_id": "bob",
            "id": "lIHALUiJSSiwVCff51KxxA",
            "type": "UserProfile",
            "update_time": "2025-06-13T16:08:25.47Z"
          }
        },
        "subject.property.email": "bob@email.com",
        "user.property.email": "bob@email.com"
      }
    }
  ]
}

Step 6

Create a CIQ Policy which designates the nodes which are allowed to read the Event nodes linked to the Organization nodes they have a relationship with, according to a specific role.

policy.jsonJson
{
  "meta": {
    "policy_version": "1.0-ciq"
  },
  "subject": {
    "type": "UserProfile"
  },
  "condition": {
    "cypher": "MATCH (user:User)<-[:HAS]-(subject:UserProfile)-[bt:BELONGS_TO]->(org:Organization)<-[:PART_OF]-(event:Event)",
    "filter": [
      {
        "operator": "AND",
        "operands": [
          {
            "operator": "=",
            "attribute": "subject.external_id",
            "value": "$subject_external_id"
          },
          {
            "operator": "=",
            "attribute": "user.external_id",
            "value": "$token.sub"
          },
          {
            "operator": "=",
            "attribute": "bt.role",
            "value": "Member"
          }
        ]
      }
    ]
  },
  "allowed_reads": {
    "nodes": [
      "event",
      "event.*",
      "org.external_id",
      "subject.property.name"
    ]
  }
}

Json to create the CIQ Policy configuration using REST.

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\":\"UserProfile\"},\"condition\":{\"cypher\":\"MATCH (user:User)<-[:HAS]-(subject:UserProfile)-[bt:BELONGS_TO]->(org:Organization)<-[:PART_OF]-(event:Event)\",\"filter\":[{\"operator\":\"AND\",\"operands\":[{\"operator\":\"=\",\"attribute\":\"subject.external_id\",\"value\":\"$subject_external_id\"},{\"operator\":\"=\",\"attribute\":\"user.external_id\",\"value\":\"$token.sub\"},{\"operator\":\"=\",\"attribute\":\"bt.role\",\"value\":\"Member\"}]}]},\"allowed_reads\":{\"nodes\":[\"event\",\"event.*\",\"org.external_id\",\"subject.property.name\"]}}",
  "status": "ACTIVE",
  "tags": []
}

Json to read the CIQ Policy configuration using REST.

policy_request.jsonJson
{
  "id": "your_policy_configuration_gid"
}

Step 7

Create a CIQ Query in the context of the policy to retrieve data.

knowledge_query.jsonJson
{
  "nodes": [
    "event"
  ],
  "filter": {
    "attribute": "subject.property.email",
    "operator": "=",
    "value": "$email"
  }
}

Json to create a CIQ Query configuration using REST.

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\":[\"event\"],\"filter\":{\"attribute\":\"subject.property.email\",\"operator\":\"=\",\"value\":\"$email\"}}",
  "status": "ACTIVE"
}

Read the CIQ Query Configuration.

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

Step 8

Run a CIQ Execution to read the data.

POST https://eu.api.indykite.com/contx-iq/v1/executeJson
{
  "id": "knowledge_query_gid",
  "input_params": {
    "email": "alice@email.com",
    "subject_external_id": "alice"
  },
  "page_token": 1
}

CIQ Execution response.

response.jsonJson
{
  "data": [
    {
      "nodes": {
        "event": {
          "Id": 8,
          "ElementId": "4:0f1c76a0-92f1-4474-80af-aa4c317e636a:8",
          "Labels": [
            "Unique",
            "Resource",
            "Event"
          ],
          "Props": {
            "_service": "capture-api",
            "create_time": "2025-06-13T16:08:11.293Z",
            "external_id": "event_lambda",
            "id": "ABeHsubWR7a3Yj0lOSuKRA",
            "type": "Event",
            "update_time": "2025-06-13T16:08:11.293Z"
          }
        }
      }
    }
  ]
}

Step 9

Create a CIQ Policy which designates the nodes which are allowed to upsert Event nodes linked to the Organization nodes they have a relationship with, according to a specific role.

policy.jsonJson
{
  "meta": {
    "policy_version": "1.0-ciq"
  },
  "subject": {
    "type": "UserProfile"
  },
  "condition": {
    "cypher": "MATCH (user:User)<-[:HAS]-(subject:UserProfile)-[bt:BELONGS_TO]->(org:Organization)",
    "filter": [
      {
        "operator": "AND",
        "operands": [
          {
            "operator": "=",
            "attribute": "subject.external_id",
            "value": "$profile_external_id"
          },
          {
            "operator": "=",
            "attribute": "$token.sub",
            "value": "$user_external_id"
          },
          {
            "operator": "=",
            "attribute": "bt.role",
            "value": "Admin"
          }
        ]
      }
    ]
  },
  "allowed_reads": {
    "nodes": [
      "org.external_id",
      "subject.property.name"
    ]
  },
  "allowed_upserts": {
    "nodes": {
      "node_types": [
        "Event"
      ]
    },
    "relationships": {
      "relationship_types": [
        {
          "type": "PART_OF",
          "source_node_label": "Event",
          "target_node_label": "Organization"
        }
      ]
    }
  }
}

Json to create the CIQ Policy configuration using REST.

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\":\"UserProfile\"},\"condition\":{\"cypher\":\"MATCH (user:User)<-[:HAS]-(subject:UserProfile)-[bt:BELONGS_TO]->(org:Organization)\",\"filter\":[{\"operator\":\"AND\",\"operands\":[{\"operator\":\"=\",\"attribute\":\"subject.external_id\",\"value\":\"$profile_external_id\"},{\"operator\":\"=\",\"attribute\":\"$token.sub\",\"value\":\"$user_external_id\"},{\"operator\":\"=\",\"attribute\":\"bt.role\",\"value\":\"Admin\"}]}]},\"allowed_reads\":{\"nodes\":[\"org.external_id\",\"subject.property.name\"]},\"allowed_upserts\":{\"nodes\":{\"node_types\":[\"Event\"]},\"relationships\":{\"relationship_types\":[{\"type\":\"PART_OF\",\"source_node_label\":\"Event\",\"target_node_label\":\"Organization\"}]}}}",
  "status": "ACTIVE",
  "tags": []
}

Json to read the CIQ Policy configuration using REST.

policy_request.jsonJson
{
  "id": "your_policy_configuration_gid"
}

Step 10

Create a CIQ Query in the context of the policy to upsert an Event node.

knowledge_query.jsonJson
{
  "nodes": [
    "event",
    "event.property.title",
    "event.property.link"
  ],
  "relationships": [],
  "upsert_nodes": [
    {
      "name": "event",
      "type": "Event",
      "external_id": "$eventId",
      "properties": [
        {
          "type": "title",
          "value": "$title"
        },
        {
          "type": "link",
          "value": "$link"
        }
      ]
    }
  ],
  "upsert_relationships": [
    {
      "name": "part",
      "source": "event",
      "target": "org",
      "type": "PART_OF"
    }
  ]
}

Json to create a CIQ Query configuration using REST.

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\":[\"event\",\"event.property.title\",\"event.property.link\"],\"relationships\":[],\"upsert_nodes\":[{\"name\":\"event\",\"type\":\"Event\",\"external_id\":\"$eventId\",\"properties\":[{\"type\":\"title\",\"value\":\"$title\"},{\"type\":\"link\",\"value\":\"$link\"}]}],\"upsert_relationships\":[{\"name\":\"part\",\"source\":\"event\",\"target\":\"org\",\"type\":\"PART_OF\"}]}",
  "status": "ACTIVE"
}

Read the CIQ Query Configuration.

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

Step 11

Run a CIQ Execution to upsert an Event node.

POST https://eu.api.indykite.com/contx-iq/v1/executeJson
{
  "id": "knowledge_query_gid",
  "input_params": {
    "profile_external_id": "bob",
    "user_external_id": "bob_user_external_id",
    "eventId": "event14",
    "title": "Event in the parking lot",
    "link": "https://www.example.com"
  },
  "page_token": 1
}

CIQ Execution response.

response.jsonJson
{
  "data": [
    {
      "nodes": {
        "event": {
          "Id": 24,
          "ElementId": "4:0f1c76a0-92f1-4474-80af-aa4c317e636a:24",
          "Labels": [
            "Unique",
            "Resource",
            "Event"
          ],
          "Props": {
            "create_time": "2025-06-13T16:23:53.388Z",
            "external_id": "event14",
            "id": "sYE-gBnaRQeRO6gZdHA59A",
            "type": "Event",
            "update_time": "2025-06-13T16:23:53.388Z"
          }
        },
        "event.property.link": "https://www.example.com",
        "event.property.title": "Event in the parking lot"
      }
    }
  ]
}

ikg