Back to all guides
ContX IQ

ContX IQ: Context-Aware Data Queries and Policies

Context-aware data queries: CIQ policies, Knowledge Queries and Execution.

What is ContX IQ (CIQ)?

ContX IQ (Contextual Intelligence Query) is IndyKite's context-aware data retrieval and mutation system. It delivers the right data, at the right time, in the right context.

Why is CIQ context-aware?

  • Query-time evaluation: Every query is evaluated against the current state of the graph.
  • Caller context: Authorization considers the caller's identity, relationships, and parameters.
  • Current state: Results reflect the graph as it exists at query time.
  • External integration: Can fetch data from external APIs via External Data Resolver.

CIQ consists of three components:

  • CIQ Policy: Defines what data can be accessed and what operations are allowed.
  • CIQ Knowledge Query: Specifies what to do with the data (read, create, update, delete).
  • CIQ Execution Query: Runs the knowledge query at runtime with specific parameters.

How do CIQ components work together?

Think of it this way:

  • Policy = What's required (graph structure) + What's allowed (permissions)
  • Knowledge Query = What to do (read/create/update/delete operations)
  • Execution Query = Run it now with these parameter values

Schema example

What credentials do I need?

  • Creating policies and knowledge queries: Service Account credentials (Config API)
  • Executing queries: AppAgent credentials + optional user access token

Configuration methods:

CIQ Policy

What is a CIQ Policy?

A CIQ Policy defines the authorization rules for data access. An administrator selects:

  • Nodes and relationships: The graph elements involved.
  • Subject node: One node must be designated as the subject (the entity requesting access).
  • Filters: Static or partial filters to constrain the data.

What are static vs partial filters?

  • Static filter: Value is hardcoded in the policy.
    Example: group.property.status = 'active'
  • Partial filter: Value is provided at execution time.
    Example: user.property.email = $user_email

Can I have multiple subject types?

No. A policy is restricted to a single subject type. If you need two subjects (e.g., Person and _Application), create two separate policies.

What is the _Application subject?

When you create an Application with credentials, IndyKite creates _Application and _AppAgent nodes in the IKG. You can use _Application as an authenticated subject.

When the subject type is _Application, add a filter with:

  • attribute: subject.external_id
  • value: $_appId (reserved value that matches the application's external_id)

CIQ Policy Syntax

{
	"meta": {
		"policy_version": "1.0-ciq"
	},
	"subject": {
		"type": "<string>"
	},
	"condition": {
		"cypher": "<string>",
		"filter": [{
			"operator": "<string>",
			"attribute": "<string>",
			"value": "<any>",
			"operands": [{
				"operator": "<string>",
				"attribute": "<string>",
				"value": "<any>",
				"operands": [{...}]
			}]
		}],
		"token_filter": {
			"operator": "<string>",
			"attribute": "<string>",
			"value": "<any>",
			"operands": [{
				"operator": "<string>",
				"attribute": "<string>",
				"value": "<any>",
				"operands": [{...}],
				"advice": {
					"error": "<string>",
					"error_description": "<string>"
				}
			}],
			"advice": {
				"error": "<string>",
				"error_description": "<string>"
			}
		}
	},
	"allowed_upserts": {
		"nodes": {
			"existing_nodes": ["<string>"],
			"node_types": ["<string>"]
		},
		"relationships": {
			"existing_relationships": ["<string>"],
			"relationship_types": [{
				"type": "<string>",
				"source_node_label": "<string>",
				"target_node_label": "<string>"
			}]
		}
	},
	"allowed_deletes": {
		"nodes": ["<string>"],
		"relationships": ["<string>"]
	},
	"allowed_reads": {
		"nodes": ["<string>"],
		"relationships": ["<string>"],
		"aggregate_values": ["<string>"]
	}
}

What does each field mean?

meta

  • policy_version: The policy syntax version. Current version is 1.0-ciq.

subject

  • type: The node type for the subject (e.g., Person, _Application).

condition

  • cypher: The Cypher query defining nodes and relationships. Supports:
    • MATCH, OPTIONAL MATCH, WHERE, WITH clauses
    • Aggregate functions (e.g., COLLECT({username: usernameProp.value}) AS usernames)
    • Inline property filters (e.g., MATCH (user:User {id: 1234}))
    Each node/relationship must have a variable name to be referenced later.

filter

Array of filters for nodes and relationships. Each filter has:

  • operator: The comparison operator (see table below)
  • attribute: The attribute to compare (omit for AND/OR/NOT)
  • value: The comparison value (omit for AND/OR/NOT). Can be hardcoded or parameterized with $ prefix. Also accepts a typed value object for non-string types - see below.
  • operands: Array of nested filters (for AND/OR/NOT). Omit if empty.

Typed filter values

A filter value may be a raw scalar (default behavior) or an explicit typed object:

{
	"attribute": "ln.property.registered_since",
	"operator": ">",
	"value": { "type": "datetime", "value": "$control_date" }
}

Supported types:

  • any (default): The value is used as-is.
  • datetime: The value must be an RFC 3339 timestamp string (e.g., "2026-01-15T00:00:00Z") or a parameter reference ($control_date).

Plain value: "..." (without a wrapping object) is still accepted and treated as any.

What operators are supported?

Operator Description Operands required
NOT Negates the nested filter Exactly 1
AND Conjunction of nested filters 1 or more
OR Disjunction of nested filters 1 or more
= Attribute equals value -
<> Attribute not equal to value -
> Attribute greater than value -
< Attribute less than value -
>= Attribute greater than or equal to value -
<= Attribute less than or equal to value -
IN Attribute is in value array -
=~ Attribute matches regex pattern -
STARTS WITH Attribute starts with value -
ENDS WITH Attribute ends with value -
IS NULL Attribute is not present or NULL -
IS NOT NULL Attribute is present and not NULL -

What attribute naming conventions are used?

Pattern Description Example
$token.<property> Property on the requestor token $token.acr
<var>.<attr> Attribute on a node or relationship user.external_id
<var>.property.<attr> Property on a node user.property.email
<var>.property.<attr>.metadata.<meta> Metadata on a property user.property.email.metadata.source

token_filter

Similar to filter, but only works with $token values. Includes:

  • advice: Step-up advice when filter is not satisfied.
    • error: Error name for the filter.
    • error_description: Description of the error.
    Returned in the response with Www-Authenticate header as "insufficient_user_authentication".

allowed_upserts

Defines what nodes and relationships derived queries can create or update.

  • nodes.existing_nodes: Node variables from cypher that can be updated. Must already exist in IKG.
  • nodes.node_types: Node labels that can be created as new nodes.
  • relationships.existing_relationships: Relationship variables from cypher that can be updated.
  • relationships.relationship_types: Relationship types that can be created:
    • type: Relationship type
    • source_node_label: Source node label
    • target_node_label: Target node label

Omit allowed_upserts or any sub-field if empty.

allowed_deletes

Defines what nodes and relationships derived queries can delete.

  • nodes: Node variables that can be deleted (e.g., person, person.* for wildcard).
  • relationships: Relationship variables that can be deleted (e.g., r1, r1.* for wildcard).

Omit allowed_deletes if empty.

allowed_reads

Defines what data derived queries can return as results.

  • nodes: Node variables that can be returned (e.g., subject, subject.*, person.property.email).
  • relationships: Relationship variables that can be returned.
  • aggregate_values: Variables from aggregate functions in cypher.

Example:

"allowed_reads": {
	"nodes": ["subject", "subject.*", "person", "person.*"]
}

This allows the Knowledge Query to use: subject, person, subject.property.email, person.property.name, etc.

CIQ Knowledge Query

What is a Knowledge Query?

A Knowledge Query specifies what operations to perform on the data. It references a policy and defines:

  • What data to read
  • What nodes/relationships to create or update
  • What nodes/relationships to delete

The query and policy are compiled into Cypher code for execution.

CIQ Knowledge Query Syntax

{
	"filter": {
		"operator": "<string>",
		"attribute": "<string>",
		"value": "<any>",
		"operands": [{
			"operator": "<string>",
			"attribute": "<string>",
			"value": "<any>",
			"operands": [{...}]
		}]
	},
	"upsert_nodes": [{
		"name": "<string>",
		"type": "<string>",
		"external_id": "<string>",
		"properties": [{
			"type": "<string>",
			"value": "<any>",
			"metadata": [{
				"type": "<string>",
				"value": "<any>"
			}]
		}]
	}],
	"upsert_relationships": [{
		"name": "<string>",
		"source": "<string>",
		"target": "<string>",
		"type": "<string>",
		"properties": [{
			"type": "<string>",
			"value": "<any>",
			"metadata": [{
				"type": "<string>",
				"value": "<any>"
			}]
		}]
	}],
	"delete_nodes": ["<string>"],
	"delete_relationships": ["<string>"],
	"nodes": ["<string>"],
	"relationships": ["<string>"],
	"aggregate_values": ["<string>"],
	"batch_read": true
}

What does each field mean?

filter

Additional filters for the query. Same structure as policy filter. Omit if empty.

upsert_nodes

Array of nodes to create or update. Omit if empty.

  • name: Variable name for the node.
    • Updating existing node: Use variable name from policy cypher.
    • Creating new node: Use a distinct name not used elsewhere.
  • type: Node label.
  • external_id: External ID for the node. Required for new nodes, omit for updates. Can be parameterized with $ prefix.
  • properties: Array of properties to set.
    • type: Property name (must be hardcoded).
    • value: Property value (hardcoded or parameterized with $).
    • metadata: Array of metadata for the property.
      • type: Metadata name (must be hardcoded).
      • value: Metadata value (hardcoded or parameterized).

upsert_relationships

Array of relationships to create or update. Omit if empty.

  • name: Variable name for the relationship.
    • Updating existing: Use variable name from policy cypher.
    • Creating new: Use a distinct name.
  • source: Source node variable name. Omit if updating existing relationship.
  • target: Target node variable name. Omit if updating existing relationship.
  • type: Relationship type. Omit if updating existing relationship.
  • properties: Array of properties (same structure as node properties).

delete_nodes

Array of node variable names to delete (e.g., car, car.property.model). Must match variables in policy cypher.

delete_relationships

Array of relationship variable names to delete (e.g., r1, r1.status). Must match variables in policy cypher.

Protected properties (cannot be deleted): _service, create_time, external_id, id, type, update_time.

nodes

Array of node variable names to return as results. Must match variables in policy cypher or names in upsert_nodes.

relationships

Array of relationship variable names to return as results. Must match variables in policy cypher or names in upsert_relationships.

aggregate_values

Array of aggregate variables (from policy cypher aggregate functions) to return as results.

batch_read

Optional boolean to enable batch mode for read queries.

  • Default: false
  • When true: Increases query timeout to 5 minutes (300 seconds).

When should I use batch_read?

  • Use when your query returns a large dataset that may exceed the default timeout.
  • Use for complex graph traversals that require more processing time.
  • Do not use for simple, fast queries where the overhead is unnecessary.

CIQ Execution Query

What is an Execution Query?

An Execution Query runs a Knowledge Query at runtime. It provides the parameter values for partial filters defined in the policy and query.

How do I execute a CIQ query?

Endpoint: POST /contx-iq/v1/execute

Authentication:

  • Header: X-IK-ClientKey: <AppAgent-credentials-token>
  • If subject type is NOT _Application: Also include a third-party bearer token.

What about _Application subjects?

When the subject is _Application:

  • The $_appId input is automatically assigned from the Application node's external_id.
  • You don't need to provide it in input_params.

Request format

{
	"id": "knowledge_query_gid",
	"input_params": {
		"subject_external_id": "subject_external_id_value",
		"token_sub": "token_sub_value",
		"license_number": "license_number_value"
	},
	"page_token": 0,
	"page_size": 100
}
  • id: The GID or name of the stored Knowledge Query.
  • input_params: Key-value pairs for each partial filter variable (without the $ prefix). String values must be 1–256 characters.
  • page_token (optional): Page index for the result set. Any value below 1 returns the first page.
  • page_size (optional): Result set size. Default is 100.

Response format

{
	"data": [
		{
			"nodes": {
				"company.external_id": "companyParking",
				"payment.external_id": "cb123",
				"subject.external_id": "subject_external_id_value"
			},
			"relationships": {
				"r1.external_id": "rel-abc"
			},
			"aggregate_values": {
				"usernames": [{"username": "alice"}, {"username": "bob"}]
			}
		}
	]
}

Each record in data may contain:

  • nodes: Values for the variables listed in the Knowledge Query nodes.
  • relationships: Values for the variables listed in the Knowledge Query relationships.
  • aggregate_values: Values for the variables listed in the Knowledge Query aggregate_values (results of Cypher aggregate functions such as COLLECT).

Fields are omitted when empty. Every response also carries an X-Indykite-Requestid header - include it in support tickets to help trace a specific call.

Next Steps