About indykite-ciq-create-node
A skill for creating new nodes in the IndyKite Graph through ContX IQ. It guides developers through policy and Knowledge Query setup, execution, and troubleshooting, while keeping updates, relationships, and deletes out of scope.
Key features
- 01Create-only CIQ policy setup
- 02Knowledge Query node upserts
- 03Parameterized IDs and properties
- 04Execute and verify node creation
- 05Troubleshooting 403 and 422 errors
Use cases
- 01Ingesting a new graph entity
- 02Authoring catalog-write policies
- 03Passing node data at execute time
- 04Diagnosing failed node creation
MCP support
The workflow can run through the MCP `ciq_execute` tool, which references the Knowledge Query.
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ indykite/ skills --skill indykite-ciq-create-nodeAfter installing, type/indykite-ciq-create-nodein Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
assets/knowledge-query-create-track.jsonpolicy-create-track.jsonreferences/execution-reference.mdknowledge-query-reference.mdpolicy-reference.mdtroubleshooting.mdscripts/execute.shDetails
From the project READMEIndyKite ContX IQ - create a new node
Create a brand-new node in the IndyKite Graph (IKG) through a ContX IQ policy + Knowledge Query, run via POST /contx-iq/v1/execute. The policy declares an allowed_upserts.nodes.node_types whitelist of node labels that may be created, and the Knowledge Query's upsert_nodes array names the node, sets its external_id, and lists the properties to write. (CIQ writes use the same policy + Knowledge Query shape as reads.)
This skill covers exactly that - node creation only. Other write paths are deliberately out of scope:
- Updating an existing node's properties uses
allowed_upserts.nodes.existing_nodesand a Knowledge Queryupsert_nodesentry that references a variable from the policy'scypher(noexternal_id). Different field, different KQ shape. - Creating relationships uses
allowed_upserts.relationships.relationship_types({type, source_node_label, target_node_label}triples) and the Knowledge Query'supsert_relationshipsarray. - Deletes use
allowed_deletesanddelete_nodes/delete_relationships.
For reads, see indykite-ciq-read.
When to use
Activate this skill when the user:
- wants to create a new node in the IKG through CIQ (e.g. ingest a new
Track,Document,Account, or other entity); - is authoring an
_Application-subject "catalog write" policy + Knowledge Query - the typical pattern for ETL / system-side ingestion; - is parameterising the new node's
external_idand properties from execute-timeinput_params; - is debugging a
403/422from aPOST /contx-iq/v1/executecall that should have created a node but didn't.
Do not activate this skill when the user:
- wants to read data from the IKG - use
indykite-ciq-read; - wants to update an existing node's properties - different policy field (
existing_nodes) and KQ shape; - wants to create relationships between nodes - different policy field (
relationship_types) and KQ array (upsert_relationships); - wants to delete anything - different policy field (
allowed_deletes) and KQ array; - is using the Capture API (
POST /capture/v1/nodes) or Terraform to ingest data instead of CIQ - those are separate ingestion paths.
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 node label the new node will use (
Track,Document,Customer, etc.) - already part of the project's data model. - For non-
_Applicationsubjects, the subject's node already in the IKG (CIQ doesn't create the subject; it authorizes against it). - A plan for
external_id- the new node's stable identifier. Two choices the caller must make every time: hard-code it in the policy/KQ (rare), or supply it as a$paramat execute time (common).
If any of these are missing, stop and tell the user - fixing them first is much cheaper than debugging a vague 403 or 422.
Steps
1. Pick the subject and 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 _Application (system-side catalog ingestion); a Person variant - for example, a user creating their own Playlist - differs only in subject.type, the filter, and the execute headers.
Cypher anchor - even a write-only policy needs a MATCH clause that anchors to the subject. The new node is not matched in cypher; it's declared in the Knowledge Query's upsert_nodes.
Working example (used throughout this skill):
System-side catalog ingestion: an
_Applicationcreates a newTracknode, supplyingexternal_id,title, andloudnessat execute time.
MATCH (subject:_Application)
That's the entire cypher - just enough to identify the subject. The Track does not appear here.
2. Author the policy with allowed_upserts.nodes.node_types
Build the policy JSON with four blocks:
meta.policy_version- currently1.0-ciq.subject.type-_Applicationfor the running example.condition.cypherandcondition.filter- anchor to the subject. For_Application, filter onsubject.external_id = $_appId(a reserved value auto-filled from the AppAgent at execute time).allowed_upserts.nodes.node_types- array of node labels the Knowledge Query may create as new nodes.
Omit allowed_reads, allowed_deletes, and the other allowed_upserts sub-fields if this policy only creates nodes. Leaving them out is the supported way to forbid those operations.
A complete create-only policy for the running example: see assets/policy-create-track.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/assets/policy-create-track.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 (operators, attribute conventions, why we omit existing_nodes and allowed_reads) see references/policy-reference.md.
3. Create the Knowledge Query with upsert_nodes
The Knowledge Query references the policy and lists what to write. Each entry in upsert_nodes describes one node to create:
name- a distinct variable name not used in the policy'scypher. This is the variable other arrays (nodes,relationships) reference.type- the node label. Must be in the policy'sallowed_upserts.nodes.node_types.external_id- required for new nodes. Hardcode for one-off writes, or use$param(the common case) so the caller supplies it at execute time.is_identity- optional boolean.truecreates the node as an identity node - see the note below.labels- optional array of extra labels attached alongsidetype.properties- array of{type, value, metadata?}items. Thetype(property name) must be hardcoded; thevaluemay be hardcoded or$param.
Echo the new node back in the response by listing its variable name in the top-level nodes array.
Identity nodes. Set
"is_identity": trueon theupsert_nodesentry - the same flag as in the Capture API. It adds theDigitalTwinlabel, which is how the IKG marks identity nodes."labels": ["DigitalTwin"]gives the same result, but prefer the flag: a misspelt key is refused when the Knowledge Query is saved, while a misspelt label is silently stored as just another label. Neither goes in the policy'snode_typeswhitelist, which checkstypeonly. 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.
A complete Knowledge Query for the running example: see assets/knowledge-query-create-track.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/assets/knowledge-query-create-track.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) - what execute and the MCP ciq_execute tool will reference.
Schema details for every Knowledge Query field, including the protected property names you cannot set: references/knowledge-query-reference.md.
4. Authenticate and execute
The execute endpoint is the same as for reads:
POST <API_URL>/contx-iq/v1/execute
Authentication for the running _Application-subject example:
X-IK-ClientKey: <AppAgent-credentials-token>- required.Authorization: Bearer …- omit for_Applicationsubjects. The AppAgent itself authenticates the subject, and$_appIdis auto-filled from the application'sexternal_id.
For Person-subject create flows, add Authorization: Bearer <user-access-token> and the policy's filter on subject.external_id = $token.sub will pin the cypher anchor to that user.
Request body:
{
"id": "<knowledge_query_gid_or_name>",
"input_params": {
"track_external_id": "track-99",
"track_title": "New Hot Track",
"track_loudness": -7.5
}
}
A runnable shell helper: scripts/execute.sh.
Full execute reference (auth combinations, response shape, error codes): references/execution-reference.md.
5. Verify the response and confirm the new node
A successful create execute returns the new node's projection:
{
"data": [
{
"nodes": {
"newTrack.external_id": "track-99",
"newTrack.property.title": "New Hot Track",
"newTrack.property.loudness": -7.5
}
}
]
}
If the response is not what you expected, walk this list before changing the policy or KQ:
- The label is whitelisted. The Knowledge Query's
upsert_nodes[].typemust be in the policy'sallowed_upserts.nodes.node_types. Mismatch →403. external_idis set. Required for new-node creation. If you're parameterising it ("$track_external_id"), the caller must supply it ininput_params. Missing →422 invalid_argument: missing or wrong input params.namedoesn't collide with a cypher variable. The variable name inupsert_nodes[].nameshould be fresh - not a name that already appears in the policy'scypher. If it collides, the policy thinks you're updating an existing match instead of creating.- Property names aren't in the protected set.
_service,create_time,external_id,id,type,update_timecannot be set as properties - they're managed by the platform. - The node didn't already exist. Re-running with the same
external_idupserts (updates) instead of creating; the response will look similar but no new node is added.
For other failure modes (auth shape wrong, malformed JSON, subject filter mismatch) see references/troubleshooting.md.
Outcome
When this skill has been applied successfully:
- A create-only CIQ policy exists in the project; it has a single
subject.type, a Cypher pattern that anchors to the subject, optional partial filters, and anallowed_upserts.nodes.node_typeswhitelist - noallowed_reads, noallowed_deletes, noexisting_nodes. - A Knowledge Query references that policy and lists exactly one new node in
upsert_nodeswith a distinctname, the righttype, anexternal_id, and the properties to set. POST /contx-iq/v1/execute(or the MCPciq_executetool) returns the new node's projection on success.- A follow-up read query (e.g. via
indykite-ciq-read) finds the new node in the IKG.
Files in this skill
references/policy-reference.md- write-focused policy schema,allowed_upserts.nodesdeep-dive (existing vs node_types), why other blocks are omitted.references/knowledge-query-reference.md-upsert_nodesschema, properties + metadata, identity nodes vialabels, protected property names, returning the new node.references/execution-reference.md-POST /contx-iq/v1/executefor writes, auth combinations including_Applicationreserved$_appId, response shape.references/troubleshooting.md-403/422/ duplicateexternal_id/ missing properties patterns.assets/policy-create-track.json- runnable create-only policy for the_Application→ newTrackexample.assets/knowledge-query-create-track.json- matching Knowledge Query.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 - see References). No Claude Code hooks, Cursor @-mentions, or Copilot workspace context are required.
References
- ContX IQ guide (developer hub)
- Music dataset tutorial - Chapter 8 "ContX IQ policies" and Chapter 9 "Knowledge Queries" - concrete read/write/delete variants against a real graph.
- 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 -
indykite_authorization_policyandindykite_knowledge_query - Credentials guide