Back to all resources
External Data Resolver External Data Resolver Json

External Data Resolver: Refresh a Node Property at Write Time from an External API

Use a CIQ upsert with external_value to write a property whose value comes from an External Data Resolver invoked at execute time. The IKG never stores stale prices: the upsert calls the pricing service, writes the result, and the same /contx-iq/v1/execute call returns the freshly-written value.

External Data Resolver: Refresh a Node Property at Write Time from an External API

External Data Resolvers can supply property values at WRITE time, not just at read time.

Pattern in this example:

1. Define an External Data Resolver config (URL, method, response_selector) pointing at an external pricing API.

2. Author a Knowledge Query whose upsert_nodes section has a property where value is replaced by external_value: "$value_source".

3. At /contx-iq/v1/execute time, input_params.value_source carries the name of the External Data Resolver config to invoke.

4. The runtime calls the External Data Resolver, takes the JSON-selected field, and writes it to the property. The query then reads the same property and returns it.

Key difference from a read-time External Data Resolver (e.g., a captured node whose property has external_value pointing at a resolver): here the property is materialized into the graph on each upsert, with metadata recording which resolver supplied it. The graph carries the audit trail; the source-of-truth stays external.

Use case

Scenario: A used-car app shows the owner the latest market valuation for their car. Prices live in an external pricing service; the IKG keeps Person -> Car -> LicenseNumber. Whenever the owner opens the car detail screen, the app calls /contx-iq/v1/execute, which:

1. Selects the subject by subject.external_id = $subject_external_id (here, "alice").

2. Walks Person -[OWNS]-> Car -[HAS]-> LicenseNumber to the car identified by $license_number.

3. Upserts car.property.current_value, sourcing the value from the External Data Resolver named in $value_source (here, "car-current-value-resolver"). Adds metadata source = $source_system (here, "pricing-api") so a later read can tell where the value came from.

4. Returns car.external_id, car.property.current_value, and the source metadata in the response.

Effect: a single execute call refreshes the value AND returns it. No client-side orchestration between "fetch from external" and "save to IKG" - CIQ handles both as one authorized transaction.

ikg

Requirements

Prerequisites:

- ServiceAccount credentials: To create the External Data Resolver config, the CIQ policy, and the Knowledge Query.

- AppAgent credentials: For graph ingest and for executing the query (X-IK-ClientKey).

- Bearer token: the subject is a Person, so /contx-iq/v1/execute requires a third-party bearer token alongside X-IK-ClientKey. The subject identity itself comes from input_params.subject_external_id.

- External pricing API: reachable from the IndyKite runtime. The response must contain the field selected by response_selector.

Required API access:

- POST /configs/v1/external-data-resolvers

- POST /capture/v1/nodes and /capture/v1/relationships

- POST /configs/v1/authorization-policies

- POST /configs/v1/knowledge-queries

- POST /contx-iq/v1/execute

Steps

Step 1: Create the External Data Resolver Configuration

- Define url, method, request/response content type, and response_selector.

- response_selector is a JSON path applied to the upstream response; whatever it selects becomes the value written by the upsert.

Step 2: Capture the Graph

- Ingest Person (alice), Car, and LicenseNumber nodes plus OWNS and HAS relationships. No current_value at capture time - the upsert will create the property at execute time.

Step 3: Create the CIQ Policy

- Subject is Person; filter binds subject.external_id to $subject_external_id.

- allowed_upserts.nodes.existing_nodes includes car so the upsert can target it.

- allowed_reads exposes car, the new property, and the source metadata.

Step 4: Create the Knowledge Query

- nodes returns car.external_id, car.property.current_value, and the source metadata.

- filter selects the right LicenseNumber via $license_number.

- upsert_nodes has one entry referencing the car variable from the policy cypher, with a single property whose external_value is "$value_source" - the External Data Resolver config name is supplied per call - and metadata source = "$source_system".

Step 5: Execute

- input_params: subject_external_id (alice), license_number (selects the car), value_source (the External Data Resolver config name), source_system (recorded as metadata).

- The runtime invokes the External Data Resolver, writes the resolved value to car.property.current_value, then returns the value and the metadata.

Step 1

External Data Resolver config for the pricing service. The demo url is the public test API https://dummyjson.com/products/1; response_selector ".price" picks the price field out of the JSON response body. Swap the url for your own pricing API.

POST https://eu.api.indykite.com/configs/v1/external-data-resolversJson
{
  "project_id": "your_project_gid",
  "description": "Returns a numeric value (the .price field) from a public test API. The CIQ upsert writes the resolved value onto car.property.current_value at execute time, so the IKG reflects the latest value without a stale ingest job. Swap the url for your own pricing API in production.",
  "display_name": "Car current-value resolver",
  "name": "car-current-value-resolver",
  "headers": {},
  "method": "GET",
  "request_content_type": "JSON",
  "request_payload": "",
  "response_content_type": "JSON",
  "response_selector": ".price",
  "url": "https://dummyjson.com/products/1"
}

Step 2

Capture the Person (alice), her Cars, and their LicenseNumber nodes. No current_value at capture time - the upsert creates it at execute time.

POST https://eu.api.indykite.com/capture/v1/nodes/Json
{
  "nodes": [
    {
      "external_id": "alice",
      "is_identity": true,
      "type": "Person",
      "properties": [
        {
          "type": "email",
          "value": "alice@email.com"
        },
        {
          "type": "name",
          "value": "Alice Smith"
        }
      ]
    },
    {
      "external_id": "kitt",
      "type": "Car",
      "properties": [
        {
          "type": "model",
          "value": "Firebird"
        }
      ]
    },
    {
      "external_id": "caddilacv16",
      "type": "Car",
      "properties": [
        {
          "type": "model",
          "value": "V16"
        }
      ]
    },
    {
      "external_id": "skodaOctavia",
      "type": "Car",
      "properties": [
        {
          "type": "model",
          "value": "Octavia"
        }
      ]
    },
    {
      "external_id": "ln-kitt-0001",
      "type": "LicenseNumber",
      "properties": [
        {
          "type": "number",
          "value": "KITT 0001"
        }
      ]
    },
    {
      "external_id": "ln-cad-007",
      "type": "LicenseNumber",
      "properties": [
        {
          "type": "number",
          "value": "CADV16-007"
        }
      ]
    },
    {
      "external_id": "ln-oct-2021",
      "type": "LicenseNumber",
      "properties": [
        {
          "type": "number",
          "value": "OCT-2021-XX"
        }
      ]
    }
  ]
}

Capture the relationships: alice OWNS each Car, and each Car HAS its LicenseNumber.

POST https://eu.api.indykite.com/capture/v1/relationships/Json
{
  "relationships": [
    {
      "source": {
        "external_id": "alice",
        "type": "Person"
      },
      "target": {
        "external_id": "kitt",
        "type": "Car"
      },
      "type": "OWNS"
    },
    {
      "source": {
        "external_id": "alice",
        "type": "Person"
      },
      "target": {
        "external_id": "caddilacv16",
        "type": "Car"
      },
      "type": "OWNS"
    },
    {
      "source": {
        "external_id": "alice",
        "type": "Person"
      },
      "target": {
        "external_id": "skodaOctavia",
        "type": "Car"
      },
      "type": "OWNS"
    },
    {
      "source": {
        "external_id": "kitt",
        "type": "Car"
      },
      "target": {
        "external_id": "ln-kitt-0001",
        "type": "LicenseNumber"
      },
      "type": "HAS"
    },
    {
      "source": {
        "external_id": "caddilacv16",
        "type": "Car"
      },
      "target": {
        "external_id": "ln-cad-007",
        "type": "LicenseNumber"
      },
      "type": "HAS"
    },
    {
      "source": {
        "external_id": "skodaOctavia",
        "type": "Car"
      },
      "target": {
        "external_id": "ln-oct-2021",
        "type": "LicenseNumber"
      },
      "type": "HAS"
    }
  ]
}

Step 3

CIQ Policy: Person subject (filtered by $subject_external_id) owns a Car that has a LicenseNumber. allowed_upserts.nodes.existing_nodes includes car so the upsert clause can write to it. allowed_reads exposes the new property and its source metadata.

policy.jsonJson
{
  "meta": {
    "policy_version": "1.0-ciq"
  },
  "subject": {
    "type": "Person"
  },
  "condition": {
    "cypher": "MATCH (subject:Person)-[:OWNS]->(car:Car)-[:HAS]->(ln:LicenseNumber)",
    "filter": [
      {
        "operator": "=",
        "attribute": "subject.external_id",
        "value": "$subject_external_id"
      }
    ]
  },
  "allowed_upserts": {
    "nodes": {
      "existing_nodes": [
        "car"
      ]
    }
  },
  "allowed_reads": {
    "nodes": [
      "car",
      "car.external_id",
      "car.property.current_value",
      "car.property.current_value.metadata.source"
    ]
  }
}

Request to create the policy.

POST https://eu.api.indykite.com/configs/v1/authorization-policiesJson
{
  "project_id": "your_project_gid",
  "description": "CIQ policy authorizing the owner of a car to upsert its current_value property. The value itself is sourced from an external pricing API at execute time via the External Data Resolver config named in input_params.value_source.",
  "display_name": "policy - owner can refresh car current_value via External Data Resolver",
  "name": "policy-extdata-current-value-write",
  "policy": "{\"meta\":{\"policy_version\":\"1.0-ciq\"},\"subject\":{\"type\":\"Person\"},\"condition\":{\"cypher\":\"MATCH (subject:Person)-[:OWNS]->(car:Car)-[:HAS]->(ln:LicenseNumber)\",\"filter\":[{\"operator\":\"=\",\"attribute\":\"subject.external_id\",\"value\":\"$subject_external_id\"}]},\"allowed_upserts\":{\"nodes\":{\"existing_nodes\":[\"car\"]}},\"allowed_reads\":{\"nodes\":[\"car\",\"car.external_id\",\"car.property.current_value\",\"car.property.current_value.metadata.source\"]}}",
  "status": "ACTIVE",
  "tags": []
}

Step 4

Knowledge Query: select the car via license number, upsert car.property.current_value with external_value referring to the External Data Resolver config (via $value_source), and return the resolved value + source metadata.

knowledge_query.jsonJson
{
  "nodes": [
    "car.external_id",
    "car.property.current_value",
    "car.property.current_value.metadata.source"
  ],
  "filter": {
    "attribute": "ln.property.number",
    "operator": "=",
    "value": "$license_number"
  },
  "upsert_nodes": [
    {
      "name": "car",
      "properties": [
        {
          "type": "current_value",
          "external_value": "$value_source",
          "metadata": [
            {
              "type": "source",
              "value": "$source_system"
            }
          ]
        }
      ]
    }
  ]
}

Request to create the Knowledge Query.

POST https://eu.api.indykite.com/configs/v1/knowledge-queriesJson
{
  "project_id": "your_project_gid",
  "description": "Find the caller's car by license number, then refresh its current_value property by invoking the External Data Resolver named in input_params.value_source. The fetched value is written to car.property.current_value with metadata recording which resolver supplied it.",
  "display_name": "knowledge query - refresh car value via External Data Resolver",
  "name": "kq-refresh-car-value",
  "policy_id": "your_policy_gid",
  "query": "{\"nodes\":[\"car.external_id\",\"car.property.current_value\",\"car.property.current_value.metadata.source\"],\"filter\":{\"attribute\":\"ln.property.number\",\"operator\":\"=\",\"value\":\"$license_number\"},\"upsert_nodes\":[{\"name\":\"car\",\"properties\":[{\"type\":\"current_value\",\"external_value\":\"$value_source\",\"metadata\":[{\"type\":\"source\",\"value\":\"$source_system\"}]}]}]}",
  "status": "ACTIVE"
}

Step 5

Execute the query. input_params: subject_external_id (alice), license_number (selects the car), value_source (the External Data Resolver config name to invoke), and source_system (recorded as metadata). Send X-IK-ClientKey, plus a bearer token since the subject is a Person.

POST https://eu.api.indykite.com/contx-iq/v1/executeJson
{
  "id": "your_query_gid_or_name",
  "input_params": {
    "subject_external_id": "alice",
    "license_number": "KITT 0001",
    "value_source": "car-current-value-resolver",
    "source_system": "pricing-api"
  }
}

Response: the freshly-written current_value plus a metadata.source field recording which system supplied it.

response.jsonJson
{
  "data": [
    {
      "nodes": {
        "car.external_id": "kitt",
        "car.property.current_value": 9.99,
        "car.property.current_value.metadata.source": "pricing-api"
      }
    }
  ]
}

API Endpoints

/configs/v1/external-data-resolvers
/capture/v1/nodes
/capture/v1/relationships
/configs/v1/authorization-policies
/configs/v1/knowledge-queries
/contx-iq/v1/execute
View OpenAPI docs

Tags

External Data Resolver ContX IQ Policy ContX IQ Query ContX IQ Execution upsert_nodes external_value Write-Time Lookup