IndyKite ContX IQ - add a property to an existing node
Set or overwrite one or more properties on a node that already exists in the IndyKite Graph (IKG), driven by a ContX IQ policy + Knowledge Query and run via POST /contx-iq/v1/execute. The policy whitelists which cypher-matched nodes may be modified (allowed_upserts.nodes.existing_nodes); the Knowledge Query's upsert_nodes references those variables (no external_id, since the node already exists) and lists the properties to set, optionally with metadata. The IKG treats this as an upsert - adding a brand-new property and overwriting an existing one are the same operation; the platform doesn't distinguish.
This skill covers exactly that - property writes on an existing node. Other paths are deliberately out of scope:
- Creating a brand-new node uses
allowed_upserts.nodes.node_typesand a Knowledge Queryupsert_nodesentry with a freshname+ anexternal_id- seeindykite-ciq-create-node. - Creating a new relationship uses
allowed_upserts.relationships.relationship_types- seeindykite-ciq-create-relationship. - Updating a relationship's properties uses
allowed_upserts.relationships.existing_relationships. - Deleting a property uses
allowed_deletes.nodeswith a<var>.property.<name>path.
For reads, see indykite-ciq-read.
When to use
Activate this skill when the user:
- wants to set a property on a node that already exists in the IKG (e.g. update a Person's
music_mood, set a LicenseNumber'sstatus, attachassurance_levelmetadata to a verified property); - is authoring a
Person-subject "update own data" policy (the canonical pattern:MATCH (subject:Person)+subject.external_id = $token.sub+existing_nodes: ["subject"]); - is authoring an
_Application-subject "system-side property write" policy that updates a node reachable from the AppAgent; - needs to write property metadata (
source,assurance_level, custom metadata fields); - is debugging a
403/422from a property-write execute that should have succeeded.
Do not activate this skill when the user:
- wants to create a brand-new node - use
indykite-ciq-create-node; - wants to link two existing nodes with a new relationship - use
indykite-ciq-create-relationship; - wants to update a relationship's properties - different policy field (
existing_relationships), out of scope here; - wants to delete a property or node - different policy field (
allowed_deletes); - wants to read data - use
indykite-ciq-read; - is using the Capture API to ingest data - different ingestion path.
Prerequisites
- An IndyKite project, AppAgent, and AppAgent credentials (the AppAgent token goes into
X-IK-ClientKeyat execute time). - A Service Account token with Config API access, and the project's GID in
PROJECT_GID- both used to create the policy and Knowledge Query. - The target node already in the IKG - CIQ doesn't seed it; this policy authorises modifying its properties.
- A clear list of property names the policy/KQ will write. Property names must be hardcoded in the KQ; only values and metadata may be
$param. - For non-
_Applicationsubjects, the subject's node also already in the IKG.
If any of these are missing, stop and tell the user - fixing them first is much cheaper than debugging a vague 403 or empty result.
Steps
1. Pick the subject and the cypher anchor
Subject type - pick one. The schema is identical across both choices; only subject.type, the filter, and the execute-time auth differ:
| Subject | Use when | Auth at execute time | Filter convention |
|---|---|---|---|
_Application |
System-side / ETL / catalog work; no user in the loop. | X-IK-ClientKey only. |
subject.external_id = $_appId (reserved). |
Person / User |
The authenticated user is performing the operation themselves. | X-IK-ClientKey + Authorization: Bearer <token>. |
subject.external_id = $token.sub. |
A policy is restricted to a single subject type - if both should be allowed, write two policies. The runnable example below uses Person ("update own profile"); an _Application variant - for example, an ETL job that backfills imported_at timestamps - differs only in subject.type, the filter, and the execute headers.
Cypher pattern - the MATCH clause that resolves the node you intend to update. The variable name you use here is what existing_nodes and upsert_nodes[].name will reference. The simplest case is MATCH (subject:Person) (the subject node itself); the more general case walks a path to a related node, e.g. MATCH (subject:Person)-[:OWNS]->(car:Car)-[:HAS]->(ln:LicenseNumber). If the exact node types, relationship types, or property spellings in the project's IKG are unknown, read them from the Data Schema API first (indykite-data-schema) - a typoed name silently matches nothing, and a write whose pattern matches nothing is a no-op that still returns 200.
Working example (used throughout this skill, modelled on the music-dataset Chapter 8 ciqpolicy4):
A
Personupdates their own profile properties (e.g.music_mood,dance_skill).
MATCH (subject:Person)
Variable: subject. The KQ will reference this name in upsert_nodes.
2. Author the policy with allowed_upserts.nodes.existing_nodes
Build the policy JSON with four blocks:
meta.policy_version- currently1.0-ciq.subject.type-Personfor the running example.condition.cypherandcondition.filter- anchor the node to update. ForPerson, filter onsubject.external_id = $token.sub.allowed_upserts.nodes.existing_nodes- array of variables fromcypherwhose properties the Knowledge Query may write. The Knowledge Query'supsert_nodes[].namemust be in this list.
Omit allowed_reads, allowed_deletes, and the other allowed_upserts sub-fields if this policy only writes properties. Combining with allowed_reads is common in practice (read-and-update-own-profile patterns) but kept out of scope here for clarity.
A complete write-only policy for the running example: see assets/policy-update-own-profile.json.
Create it through the Config API:
# set the current project_id, and stringify only the `policy` field, before POSTing
jq --arg pid "$PROJECT_GID" '.project_id = $pid | .policy |= tojson' indykite-ciq-add-property/assets/policy-update-own-profile.json \
| curl -X POST "$API_URL/configs/v1/authorization-policies" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $SERVICE_ACCOUNT_TOKEN" \
-d @-
A 201 Created returns the policy's id (GID). Export it as POLICY_ID - the Knowledge Query create injects it into policy_id.
For the full schema (why we omit node_types, the _Application variant, the metadata variant from policyMetaData) see references/policy-reference.md.
3. Create the Knowledge Query with upsert_nodes
The Knowledge Query references the policy. Each entry in upsert_nodes describes one node-update:
name- must match a variable from the policy'scypher(e.g.subject,car,ln). This is what differs structurally from the create-node skill; using a fresh name here would imply create.type- omit when updating an existing node. (For creates it would specify the new node's label; for updates the label is whatever the matched node already has.)external_id- omit. Required only for creates.properties- array of{type, value, metadata?}items.type(property name) is hardcoded;valuemay be hardcoded or$param;metadatais optional and follows the same rules.
Echo the result back in the response by listing properties to project in the top-level nodes array, e.g. subject.property.music_mood. This confirms the value that was written.
A complete Knowledge Query for the running example: see assets/knowledge-query-update-own-profile.json.
Create it through the Config API:
# set the current project_id and policy_id, and stringify only the `query` field, before POSTing
jq --arg pid "$PROJECT_GID" --arg polid "$POLICY_ID" '.project_id = $pid | .policy_id = $polid | .query |= tojson' indykite-ciq-add-property/assets/knowledge-query-update-own-profile.json \
| curl -X POST "$API_URL/configs/v1/knowledge-queries" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $SERVICE_ACCOUNT_TOKEN" \
-d @-
A 201 Created returns the Knowledge Query's id (GID).
Schema details - including the protected property names you cannot set (_service, create_time, external_id, id, type, update_time), the metadata sub-array, and the rich knowledgeQueryMetaData example with $token.iss substitution - live in references/knowledge-query-reference.md.
4. Authenticate and execute
The execute endpoint is the same as for reads, node-creates, and relationship-creates:
POST <API_URL>/contx-iq/v1/execute
Authentication for the running Person-subject example:
X-IK-ClientKey: <AppAgent-credentials-token>- required.Authorization: Bearer <user-access-token>- required. The token'ssubclaim drives$token.subin the policy filter, pinning the cypher anchor to that one user.
For _Application-subject property writes, omit the Bearer header; the reserved $_appId is auto-filled from the AppAgent.
Request:
{
"id": "<knowledge_query_gid_or_name>",
"input_params": {
"new_music_mood": "Acoustic Sadness",
"new_dance_skill": 0.67
}
}
A runnable shell helper: scripts/execute.sh.
Full execute reference (auth combinations, response shape, error semantics): references/execution-reference.md.
5. Verify the response and confirm the property write
A successful property-write execute echoes the projection you listed in the KQ's nodes array:
{
"data": [
{
"nodes": {
"subject.property.music_mood": "Acoustic Sadness",
"subject.property.dance_skill": 0.67
}
}
]
}
If the response is not what you expected, walk this list before changing the policy or KQ:
- Variable in
existing_nodes. The KQ'supsert_nodes[].namemust be in the policy'sallowed_upserts.nodes.existing_nodes. Mismatch →403. - Cypher matched a node. If the cypher returns no rows (e.g. the user's
external_idisn't seeded as a Person), the upsert has nothing to attach to -200with emptydata. namematches a cypher variable. Using a fresh name (one not in cypher) makes the platform interpret the entry as a create - usually rejected because the matchingnode_typeswhitelist isn't there.- No
external_idin theupsert_nodesentry. Includingexternal_idflips the operation to "create" semantics. For property writes on an existing match, omit it. - Property names not protected.
_service,create_time,external_id,id,type,update_timecannot be set as properties - they're managed by the platform. - Property value type matches the IKG schema. Sending
"-7.5"(string) for a numeric property is rejected.
For other failure modes (auth shape wrong, missing input_params, metadata weirdness) see references/troubleshooting.md.
Outcome
When this skill has been applied successfully:
- A property-write CIQ policy exists; it has a single
subject.type, a Cypher pattern that resolves to the node to update, optional partial filters, and anallowed_upserts.nodes.existing_nodeswhitelist - nonode_types, noallowed_reads, noallowed_deletes. - A Knowledge Query references that policy and lists
upsert_nodesentries that reuse cypher variable names, omitexternal_id, and declare the properties (and optional metadata) to set. POST /contx-iq/v1/executereturns the projected property values, confirming the write.- A follow-up read query (e.g. via
indykite-ciq-read) finds the new property values on the node.
Files in this skill
references/policy-reference.md- write-focused policy schema,existing_nodesdeep-dive, the Person and_Applicationpatterns, why other blocks are omitted.references/knowledge-query-reference.md-upsert_nodesfor updates (variable from cypher, noexternal_id), properties + metadata, theknowledgeQueryMetaDatarich example, protected property names.references/execution-reference.md-POST /contx-iq/v1/executefor property writes, auth combinations, response shape including the richPropsblock.references/troubleshooting.md-403/ empty-data/ type-mismatch / metadata patterns.assets/policy-update-own-profile.json- runnable Person-subject "update own profile" policy, modelled on music-dataset Chapter 8ciqpolicy4.assets/knowledge-query-update-own-profile.json- matching Knowledge Query (setsmusic_moodanddance_skill).scripts/execute.sh- Bash helper that posts to/contx-iq/v1/executewith the right headers.
Agent-specific notes
This skill uses generic markdown instructions and works across all agents listed in the README. The agent needs to be able to issue HTTP requests (curl, an HTTP client, or the IndyKite Terraform provider). No Claude Code hooks, Cursor @-mentions, or Copilot workspace context are required.
References
- ContX IQ guide (developer hub) - full schema, including
allowed_upserts.nodes.existing_nodesand theproperties/metadataarrays. - Music dataset tutorial - Chapter 8 "ContX IQ policies" -
ciqpolicy4is the canonical Person-subject "update own data" pattern;kq4bis its write variant. - Music dataset tutorial - Chapter 9 "Knowledge Queries" - read/write/delete variant naming convention (
kq/kqb/kqc). - Developer-hub resources - CIQ examples -
policyMetaData+knowledgeQueryMetaDatashow a richer property-write pattern with$token.isssubstitution and per-property metadata. - Config API documentation
- Cypher query language manual (Neo4j; openCypher) - the graph query language used in CIQ policy and Knowledge Query conditions over the IndyKite Knowledge Graph.
- IndyKite Terraform provider
- Credentials guide