IndyKite ContX IQ - create a new node + link it to existing nodes
Create a brand-new node in the IndyKite Graph (IKG) and wire it to one or more existing nodes in a single atomic POST /contx-iq/v1/execute call. The policy whitelists both a node label and one or more relationship triples, and the Knowledge Query carries both upsert_nodes (for the new node) and upsert_relationships (for the new edge(s)); the new node's variable name from upsert_nodes is referenced as the source or target in upsert_relationships. It combines the patterns from indykite-ciq-create-node and indykite-ciq-create-relationship.
This is the canonical "ingest a new entity into the graph" pattern - used in the IndyKite developer-hub resources for the insurance Contract example (policyAllowWriteContract + knowledgeQueryAllowWriteContract), where one execute creates a new Contract node and wires it via two relationships (COVERS to a Vehicle, ACCEPTED from a Person).
Other paths are deliberately out of scope:
- Just creating a node, no link - use
indykite-ciq-create-node. - Just linking two existing nodes - use
indykite-ciq-create-relationship. - Updating an existing node's properties or relationship's properties - different operations entirely.
When to use
Activate this skill when the user:
- wants to ingest a new entity through CIQ in one atomic operation (create the node and its relationships to existing nodes);
- is implementing the canonical insurance/contract pattern: a new
Contractnode linked to an existingVehicleand an existingPerson; - is building an "add a comment to a document" flow: a new
Commentnode linked to an existingDocument; - is parameterising both the new node's
external_idand the source/target endpoints frominput_params; - is debugging a
403/422from a combined create execute that should have wired the new node up.
Do not activate this skill when the user:
- only needs to create a node - use
indykite-ciq-create-node; - only needs to link two existing nodes - use
indykite-ciq-create-relationship; - needs to write properties on existing elements - use the property-write skills.
Prerequisites
- An IndyKite project, AppAgent, and AppAgent credentials.
- 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 endpoint nodes the new node will link to already in the IKG.
- The node label and relationship label(s) the operation will use, allowed by the project's data model.
- A plan for the new node's
external_id- usually parameterised via$param.
Steps
1. Pick the subject and the cypher pattern
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 _Application (insurance-contract ingestion); a Person variant - for example, a user posting a new Comment linked to an existing Document they own - differs only in subject.type, the filter, and the execute headers.
Cypher pattern - must MATCH the subject and every existing endpoint the new node will link to. The new node itself is not matched; it's declared in upsert_nodes. 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, taken verbatim from the developer-hub policyAllowWriteContract resource):
An
_Applicationcreates a newContractnode and links it via:COVERSto an existingVehicle(owned by an existingCompany) and via:ACCEPTEDfrom an existingPerson.
MATCH (subject:_Application)-[r1:HAS_AGREEMENT_WITH]->(company:Company)-[r2:OWNS]->(vehicle:Vehicle)
MATCH (person:Person)
Variables: subject, r1, company, r2, vehicle, person. The new Contract node will be declared as a fresh name in upsert_nodes; the two new relationships will reference vehicle, person, and the fresh name as endpoints.
2. Author the policy with both node_types and relationship_types
Build the policy JSON with five blocks:
meta.policy_version- currently1.0-ciq.subject.type-_Applicationfor the running example.condition.cypherandcondition.filter- the cypher matches the subject and existing endpoints; the filter pins them byexternal_id($_appIdplus$vehicleID,$personID).allowed_upserts.nodes.node_types- the new node's label (e.g.["Contract"]).allowed_upserts.relationships.relationship_types- one triple per new relationship, matching the directions and labels.
A complete combined-create policy for the running example: see assets/policy-create-contract.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-create-node-with-link/assets/policy-create-contract.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 schema deep-dive (how node_types and relationship_types interact, why direction matters, what existing_nodes would add) see references/policy-reference.md.
3. Create the Knowledge Query with both upsert_nodes and upsert_relationships
The Knowledge Query has two write arrays:
upsert_nodes - declares the new node. Same shape as in indykite-ciq-create-node:
name- fresh variable name (not in cypher), e.g.contract.type- node label, must matchallowed_upserts.nodes.node_types.external_id- required for new nodes; usually$param.labels- optional array of extra labels attached alongsidetype. Chiefly used to create identity nodes - see the note below.properties- array of{type, value, metadata?}items.
Identity nodes. The Knowledge Query has no
is_identityfield - that flag belongs to the Capture API. In the IKG, identity status is carried by theDigitalTwinlabel; Capture'sis_identity: trueis shorthand for adding it at ingest. The CIQ equivalent is"labels": ["DigitalTwin"]on theupsert_nodesentry. The label goes inlabelsonly - the policy'snode_typeswhitelist checkstype, soDigitalTwinis never listed there. Create the node as an identity node whenever it must act as a2.0-kbacsubject: a non-identity subject makes every2.0-kbacdecision silentlyfalse(3.0-kbacdoes not require it). To confirm the label landed, run a2.0-kbacevaluation with the new node as subject.
upsert_relationships - declares each new relationship. Same shape as in indykite-ciq-create-relationship, with one important twist:
name- fresh variable name for each new relationship (e.g.r3,r4).source- variable name. Can be a cypher variable (existing node) or thenameof anupsert_nodesentry (the just-created node).target- same: cypher variable orupsert_nodesname.type- must match the policy'srelationship_types.
That source/target flexibility is what makes the combined operation work: r3 connects the just-created contract to the existing vehicle; r4 connects the existing person to the just-created contract.
A complete combined-create Knowledge Query for the running example: see assets/knowledge-query-create-contract.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-create-node-with-link/assets/knowledge-query-create-contract.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 (which arrays interact, response shape covering both new nodes and new relationships) live in references/knowledge-query-reference.md.
4. Authenticate and execute
The execute endpoint is the same as for every other CIQ operation:
POST <API_URL>/contx-iq/v1/execute
For the _Application subject:
X-IK-ClientKey: <AppAgent-credentials-token>- required.Authorization: Bearer …- omit.
Request:
{
"id": "<knowledge_query_gid_or_name>",
"input_params": {
"vehicleID": "car2",
"personID": "ryan",
"contract_external_id": "ct853",
"contractNumber": "rbjh853"
}
}
A runnable shell helper: scripts/execute.sh.
Full execute reference: references/execution-reference.md.
5. Verify the response and confirm the wiring
A successful combined-create returns the new node's projection plus the new relationships' identifiers:
{
"data": [
{
"nodes": {
"contract.external_id": "ct853",
"contract.property.number": "rbjh853"
},
"relationships": {
"r3": { "Id": …, "ElementId": "…", "StartId": …, "EndId": … },
"r4": { "Id": …, "ElementId": "…", "StartId": …, "EndId": … }
}
}
]
}
If the response is not what you expected, walk this list:
- Both whitelist entries present. The KQ's
upsert_nodes[].typemust be inallowed_upserts.nodes.node_types, and eachupsert_relationships[]triple must be inallowed_upserts.relationships.relationship_types. Either mismatch →403. - Endpoints exist. Every cypher variable the relationships reference (
vehicle,person) must resolve to a real node. IfMATCHfinds no rows, the operation has nothing to wire -200with emptydata. - Cross-references match. The new node's
nameinupsert_nodes(e.g.contract) must be exactly the same string used inupsert_relationships[].sourceortarget. Typos here silently produce wiring failures. - Direction matches. Relationship triples encode direction.
(Contract)-[:COVERS]->(Vehicle)is different from(Vehicle)-[:COVERS]->(Contract). - All
$params present. Thecontract_external_id,contractNumber,vehicleID,personIDall need to be ininput_params.
For other failure modes see references/troubleshooting.md.
Outcome
When this skill has been applied successfully:
- A combined-create CIQ policy exists; it has a single
subject.type, a Cypher pattern matching the subject and existing endpoint nodes, partial filters, and bothallowed_upserts.nodes.node_typesandallowed_upserts.relationships.relationship_typespopulated. - A Knowledge Query references the policy and lists the new node in
upsert_nodesand one or more new relationships inupsert_relationships(with the new node'snamereferenced as asourceortarget). - One
POST /contx-iq/v1/executereturns the new node's projection plus the new relationships' identifiers. - A follow-up read confirms the new entity is wired into the graph.
Files in this skill
references/policy-reference.md- combinednode_types+relationship_types, optionalexisting_nodesfor hybrid create-and-update flows.references/knowledge-query-reference.md-upsert_nodes+upsert_relationshipsinteraction,source/targetcross-referencing, identity nodes vialabels, multi-relationship patterns.references/execution-reference.md- request/response, atomicity guarantees, idempotence on rerun.references/troubleshooting.md-403/ empty-data / wiring-mismatch / cross-reference patterns.assets/policy-create-contract.json- the canonical insurance-Contract example, lifted frompolicyAllowWriteContractin the developer-hub resources.assets/knowledge-query-create-contract.json- matching Knowledge Query.scripts/execute.sh- Bash helper.
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. No Claude Code hooks, Cursor @-mentions, or Copilot workspace context are required.
References
- ContX IQ guide (developer hub) - full schema, including how
upsert_nodesandupsert_relationshipscross-reference. - Developer-hub resources -
policyAllowWriteContractandknowledgeQueryAllowWriteContract- the canonical insurance-Contract example this skill is built around. - Music dataset tutorial - Chapter 9 "Knowledge Queries" -
kqbwrite variants for context-aware ingestion patterns. - 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