Back to all guides
External Data

External Data Resolver: Fetch External API Data During Queries

How to configure External Data Resolvers and Data References to fetch data from external APIs during ContX IQ query execution.

What is an External Data Resolver?

An External Data Resolver (EDR, called a data resolver in the Hub) is a configuration that fetches data from an external HTTP endpoint at runtime, during ContX IQ query execution or an AuthZEN authorization decision. A Data Reference is the property on a node that points to the resolver using external_value. Together, they enable real-time data lookups without storing sensitive data in the IKG.

Data references exist for data that must stay outside the IKG - on-premise systems, data covered by residency or privacy rules - but still has to influence what a query returns or whether access is granted. The referenced value is fetched when it is needed and used in that request only; it is never written to the IKG. This also holds for a ContX IQ upsert whose property uses external_value: the upsert stores the resolver reference on the property, and the value is fetched again whenever that property is read.

Key benefits:

  • Keep sensitive data external: Store VINs, SSNs, or other sensitive data in your secure systems.
  • Real-time lookups: Always get current data, not stale copies.
  • Single source of truth: Avoid data duplication and sync issues.
  • Hybrid queries: Combine IKG graph data with external API responses.

How does it work?

The flow involves three components working together:

  • External Data Resolver: Configuration defining how to call an external API (URL, method, headers, response mapping).
  • Data Reference: A node property using external_value that references the resolver instead of storing a value directly.
  • ContX IQ query or KBAC decision: When a Knowledge Query returns a data-reference property, or a 2.0-kbac policy condition touches one, the platform calls the configured resolver and substitutes the fetched value. A 3.0-kbac policy runs its Cypher unmodified and cannot resolve data references; creating one that references a resolver-backed property fails with external properties cannot be used in data-residency policies.

External Data Resolver & Data Reference Flow

Example scenario

A car rental app needs vehicle VIN numbers, but VINs are stored in a separate vehicle registry:

  • IKG stores: Person(Alice) -[ACCEPTED]-> Contract -[COVERS]-> Vehicle(car2) with category="Car"
  • External registry stores: car2.vin = "1HGBH41JXMN109186"

Query execution:

  1. User (Alice) requests vehicle details including VIN
  2. ContX IQ policy authorizes: Alice can READ vehicles covered by her contracts
  3. Query fetches category from IKG: "Car"
  4. Query detects data reference (external_value) on vin property
  5. Resolver calls external API to fetch VIN
  6. Response: {category: "Car", vin: "1HGBH41JXMN109186"}

What credentials do I need?

  • Creating resolvers: Service Account credentials (Config API)
  • Ingesting nodes with data references: AppAgent credentials (Capture API)
  • Executing queries: AppAgent credentials + optional user access token

Configuration methods:

External Data Resolver Configuration

REST API Endpoints

Operation Method Endpoint
Create POST /configs/v1/external-data-resolvers
Read by ID GET /configs/v1/external-data-resolvers/{id}
Read by name GET /configs/v1/external-data-resolvers/{name}?location={project_id}
List all GET /configs/v1/external-data-resolvers?project_id={id}
Update PUT /configs/v1/external-data-resolvers/{id}
Delete DELETE /configs/v1/external-data-resolvers/{id}

Create Request Syntax

{
  "project_id": "<string>",
  "name": "<string>",
  "display_name": "<string>",
  "description": "<string>",
  "url": "<string>",
  "method": "<string>",
  "headers": {
    "<header_name>": [
      "<value1>",
      "<value2>"
    ]
  },
  "request_content_type": "<string>",
  "request_payload": "<string>",
  "response_content_type": "<string>",
  "response_selector": "<string>"
}

What does each field mean?

Required fields

  • project_id: The GID of the project where the resolver will be created.
  • name: Unique, immutable identifier for the resolver. Used in data references (external_value).
  • url: The full endpoint URL to invoke. Supports parameter substitution (see below).
  • method: HTTP method. Supported values: GET, POST, PUT, PATCH.
  • request_content_type: Content type for requests. Currently only JSON is supported.
  • response_content_type: Content type for responses. Currently only JSON is supported.
  • response_selector: JSON path to extract data from the response (e.g., .data, .result.value).

Optional fields

  • display_name: Human-readable name (can be updated).
  • description: Description of the resolver (max 65000 UTF-8 bytes).
  • headers: HTTP headers to include in requests. Each header can have multiple values.
  • request_payload: JSON body for POST/PUT/PATCH requests (as a string).

Field constraints (REST)

Field Constraint
display_name 2-254 characters. Equals name when not set. On PUT, omitted or null keeps the current value; an empty string removes it.
description 2-65000 characters. Same PUT semantics as display_name.
headers Object mapping a header name to an array of at least one value, each value 1-255 characters. Placeholders are not substituted in headers.
request_content_type Sets the outgoing Content-Type header. A Content-Type entry in headers is overridden.
response_content_type Sets the outgoing Accept header (overriding one in headers). The endpoint must answer with Content-Type: application/json, otherwise the lookup fails.
response_selector 1-255 characters.
name, project_id Immutable; not part of the PUT body.

URL Parameter Substitution

The URL can include dynamic parameters using the {$...} syntax:

{
  "url": "https://api.example.com/vehicles/{$vehicle_id}/vin?format={$format || json}"
}
  • {$vehicle_id}: Substituted at runtime with the vehicle_id input parameter of the request.
  • {$format || json}: Uses "json" as default if format parameter is not provided.

Placeholder rules

Placeholder values come from the input parameters of the request that triggers the lookup: input_params on POST /contx-iq/v1/execute, or context.input_params on an AuthZEN request. Pass whatever the endpoint needs (an identifier, a locale, a date) as an input parameter of that request.

URL placeholders (url)

  • Format: {$variable_name} or {$variable_name || default value}. The default is used when the input parameter is absent.
  • Allowed in the path after the first / and in the query string, e.g. https://api.example.com/{$id} or https://api.example.com/?a={$id}.
  • The variable name may contain dots ({$vehicle.id}); dots are part of the name, not nesting.
  • Only string, int, float, and bool input values can be substituted. Values are URL-escaped for their position (path or query).
  • A placeholder with no input value and no default fails the lookup with variable '$name' not defined, nor default value is set.

End-to-end example:

url:              https://example.com/{$inPath}/test?a={$q1}&b={$q2 || Hello world!}
input_params:     {"inPath": 456, "q1": "X@b"}
Request sent to:  https://example.com/456/test?a=X%40b&b=Hello+world!

Body placeholders (request_payload)

  • A placeholder is a whole JSON string value of the form "$variable_name" or "$variable_name || default". It is replaced by the input parameter's JSON value, which may be a scalar, an array, or an object.
  • The default must be valid JSON. Because it sits inside a string, quotes must be escaped: "$v || \"text\"", "$v || 1.23e-10", "$v || {\"key\": \"value\"}".
  • Placeholders work anywhere a string value can appear, including inside arrays. Object keys, numbers, booleans, and null are never substituted.

End-to-end example:

// request_payload
{
  "scalar": "$s1Val",
  "key": "$k1Val",
  "arr": [
    1,
    2,
    "$a1Val || 3"
  ],
  "obj": "$o1Val",
  "defaultObj": "$o2Val || {\"key\": \"value\"}"
}
// input_params
{
  "s1Val": "Hello, World!",
  "k1Val": [
    "x",
    "y",
    "z"
  ],
  "o1Val": {
    "newArr": [
      5,
      5,
      5
    ]
  }
}
// body sent to the endpoint
{
  "scalar": "Hello, World!",
  "key": [
    "x",
    "y",
    "z"
  ],
  "arr": [
    1,
    2,
    3
  ],
  "obj": {
    "newArr": [
      5,
      5,
      5
    ]
  },
  "defaultObj": {
    "key": "value"
  }
}

Response Selector

The response_selector uses jq-like JSON path syntax to extract values:

Selector API Response Extracted Value
.vin {"vin": "ABC123"} "ABC123"
.data.value {"data": {"value": "XYZ"}} "XYZ"
.results[0] {"results": ["first", "second"]} "first"
.echo {"echo": {"nested": "data"}} {"nested": "data"}

Selector rules

  • The selector must start with .. A bare . returns the whole response.
  • Only key access and array indexing are supported: .key, ."quoted key", [0], and chains of them. Negative indexes count from the end ([-1]). No pipes, functions, filters, or slices.
  • Keys containing dots, quotes, or brackets must be quoted: ."dotted.key.within"[1]. Purely numeric keys work unquoted: .specialCases.123.
  • The selected value keeps its JSON type: string, number, boolean, null, array, or object.
  • Selecting a key on a non-object, an index on a non-array, or an index out of bounds fails the lookup with an invalid query: ... error.

Given this response:

{
  "text": "abc",
  "obj": {
    "secondLevel": [
      {
        "someValue": 123
      },
      {
        "someValue": 456.789
      },
      {
        "someValue": [
          "value1",
          "value1.1"
        ]
      },
      {
        "someValue": null
      },
      {
        "someValue": false
      }
    ],
    "arrayOfArrays": [
      [
        1,
        2,
        3
      ],
      [
        4,
        5,
        6
      ],
      [
        7,
        8,
        9
      ]
    ],
    "specialCases": {
      "123": "pure numeric key",
      "super-'key'[1]": "value",
      "dotted.key.within": [
        "a",
        "b",
        "c"
      ]
    }
  }
}
Selector Result
.text"abc"
.obj.secondLevel[0].someValue123
.obj.secondLevel[2].someValue[1]"value1.1"
.obj.secondLevel[3].someValuenull
.obj.arrayOfArrays[1][1]5
.obj.arrayOfArrays[0][1, 2, 3]
.obj.arrayOfArrays[-1][-1]9
.obj.specialCases.123"pure numeric key"
.obj.specialCases."super-'key'[1]""value"
.obj.specialCases."dotted.key.within"[1]"b"

Example: Create External Data Resolver

{
  "project_id": "gid:AAAABbbbCCCC...",
  "name": "vin-lookup-resolver",
  "display_name": "Vehicle VIN Lookup",
  "description": "Fetches vehicle VIN from external registry",
  "url": "https://vehicle-registry.example.com/api/v1/vehicles/{$vehicle_id}",
  "method": "GET",
  "headers": {
    "Authorization": [
      "Bearer api-key-12345"
    ],
    "X-API-Version": [
      "2024-01"
    ]
  },
  "request_content_type": "JSON",
  "response_content_type": "JSON",
  "response_selector": ".data.vin"
}

Example: POST Request with Payload

{
  "project_id": "gid:AAAABbbbCCCC...",
  "name": "enrichment-service",
  "display_name": "Data Enrichment Service",
  "description": "Enriches node data via POST request",
  "url": "https://enrichment.example.com/api/enrich",
  "method": "POST",
  "headers": {
    "Authorization": [
      "Bearer secret-token"
    ]
  },
  "request_content_type": "JSON",
  "request_payload": "{\"lookup_type\": \"vehicle\", \"fields\": [\"vin\", \"registration\"]}",
  "response_content_type": "JSON",
  "response_selector": ".enriched_data"
}

Using external_value (Data References) in Nodes

What is external_value?

A data reference is created when you use external_value instead of value for a property. The external_value points to an External Data Resolver, which fetches the actual value from an external system when queried via ContX IQ.

A property carries either value or external_value, never both. Property metadata (source, assurance_level, and so on) can be attached to a data reference like to any other property.

Prefer the name over the GID. A resolver's name can be reused: you can delete and recreate the configuration, or change its url, without touching the nodes that reference it. A GID is bound to one configuration object and goes stale when that object is deleted.

The resolver does not have to exist yet at ingest time. POST /capture/v1/nodes accepts a data reference to any name. The reference is only checked when a query or decision tries to fetch the value; if no configuration with that name exists at that moment, the request fails (see Runtime errors).

Deleting a resolver does not remove its data references. After DELETE /configs/v1/external-data-resolvers/{id}, every node still carrying "external_value": "<name>" makes any query or decision that reads that property fail. Either recreate a resolver with the same name, or first remove the property from the affected nodes with POST /capture/v1/nodes/properties/delete.

How can I reference a resolver?

The external_value accepts multiple reference formats:

Format Example Description
By name "external_value": "vin-lookup-resolver" Reference resolver by its unique name
By GID "external_value": "gid:AAAABbbbCCCC..." Reference resolver by its configuration ID
Parameter "external_value": "$resolver_ref" Dynamic reference via input parameter (in Knowledge Query upserts)

Node with external_value Syntax

Using resolver name (string):

{
  "external_id": "car2",
  "type": "Vehicle",
  "properties": [
    {
      "type": "category",
      "value": "Car"
    },
    {
      "type": "vin",
      "external_value": "vin-lookup-resolver"
    }
  ]
}

Using resolver GID:

{
  "external_id": "car2",
  "type": "Vehicle",
  "properties": [
    {
      "type": "category",
      "value": "Car"
    },
    {
      "type": "vin",
      "external_value": "gid:AAAABWtpcmtlby1jb25maWcAACRleHRlcm5hbC1kYXRhLXJlc29sdmVyL..."
    }
  ]
}

In these examples:

  • category has a regular value stored in the IKG.
  • vin uses external_value pointing to the resolver (by name or GID).

Capture API Request

POST /capture/v1/nodes
{
  "nodes": [
    {
      "external_id": "alice",
      "is_identity": true,
      "type": "Person",
      "properties": [
        {
          "type": "email",
          "value": "alice@email.com"
        },
        {
          "type": "given_name",
          "value": "Alice"
        }
      ]
    },
    {
      "external_id": "car2",
      "type": "Vehicle",
      "properties": [
        {
          "type": "category",
          "value": "Car"
        },
        {
          "type": "is_active",
          "value": true
        },
        {
          "type": "vin",
          "external_value": "vin-lookup-resolver"
        }
      ]
    }
  ]
}

ContX IQ Integration

Policy for External Data Access

The CIQ policy defines the graph pattern and what can be read. External data properties are accessed through the same allowed_reads as regular properties.

{
  "meta": {
    "policy_version": "1.0-ciq"
  },
  "subject": {
    "type": "Person"
  },
  "condition": {
    "cypher": "MATCH (company:Company)-[:OFFERS]->(contract:Contract)<-[:ACCEPTED]-(subject:Person), (contract)-[:COVERS]->(vehicle:Vehicle)",
    "filter": [
      {
        "attribute": "subject.external_id",
        "operator": "=",
        "value": "$subject_external_id"
      }
    ]
  },
  "allowed_reads": {
    "nodes": [
      "vehicle",
      "vehicle.*"
    ]
  }
}

The vehicle.* wildcard allows reading all vehicle properties, including data references (properties with external_value).

Knowledge Query Requesting External Data

The knowledge query specifies which properties to return. External value properties are requested the same way as regular properties:

{
  "nodes": [
    "vehicle",
    "vehicle.property.category",
    "vehicle.property.vin"
  ]
}

When executed:

  • vehicle.property.category: Returns value from IKG ("Car")
  • vehicle.property.vin: Triggers resolver call, returns external value ("vinmagic")

Execution and Response

Execute the query via ContX IQ:

POST /contx-iq/v1/execute
{
  "id": "knowledge_query_gid",
  "input_params": {
    "subject_external_id": "alice"
  }
}

Response with combined IKG + external data:

{
  "data": [
    {
      "nodes": {
        "vehicle": {
          "Labels": [
            "Resource",
            "Vehicle"
          ],
          "Props": {
            "external_id": "car2",
            "type": "Vehicle"
          }
        },
        "vehicle.property.category": "Car",
        "vehicle.property.vin": "vinmagic"
      }
    }
  ]
}

Terraform Configuration

Use the indykite_external_data_resolver resource:

resource "indykite_external_data_resolver" "vin_lookup" {
  location          = indykite_application_space.my_app_space.id
  name              = "vin-lookup-resolver"
  display_name      = "Vehicle VIN Lookup"
  description       = "Fetches vehicle VIN from external registry"
  url               = "https://vehicle-registry.example.com/api/v1/vehicles"
  method            = "GET"
  request_type      = "json"
  response_type     = "json"
  response_selector = ".data.vin"
  headers {
    name   = "Authorization"
    values = ["Bearer ${var.api_key}"]
  }
  headers {
    name   = "X-API-Version"
    values = ["2024-01"]
  }
}

Terraform Arguments Reference

Argument Required Description
location Yes Application Space ID where the resolver is created
name Yes Unique, immutable identifier
url Yes External API endpoint URL
method Yes HTTP method (GET, POST, PUT, PATCH)
request_type Yes Request content type (json)
response_type Yes Response content type (json)
response_selector Yes JSON path to extract value
display_name No Human-readable name
description No Resource description
headers No Request headers block
request_payload No JSON body for POST/PUT/PATCH

Error Handling

HTTP Code Meaning Common Cause
400 Bad Request Invalid JSON, missing required fields
401 Unauthorized Invalid or missing Bearer token
403 Forbidden Insufficient permissions for the project
404 Not Found Resolver ID/name doesn't exist
412 Precondition Failed ETag mismatch (concurrent modification)
422 Unprocessable Entity Validation error (invalid URL, method, etc.)

Runtime errors on POST /contx-iq/v1/execute

When a lookup fails while a query is executing, the whole request fails with 424 Failed Dependency and a message naming the cause. No partial result is returned.

Message Cause and fix
data resolver configuration by name was not found: <name> A node references a resolver name that does not exist in this project. Create the resolver or remove the property.
data resolver configuration by gid was not found: <gid> The referenced configuration object was deleted. Re-ingest the property with the new name or GID.
invalid external data resolver GID identifier: <value> The external_value starts with gid: but is not a resolver GID.
variable '$name' not defined, nor default value is set A placeholder in url or request_payload has no matching input parameter and no default.
invalid value for '$name': unsupported type ... A URL placeholder received an array or object. Only string, int, float, and bool go into the URL.
unexpected status code: <code> - <status> The endpoint answered outside 2xx. Check url, headers, and the substituted values.
unexpected content type: <type> The endpoint did not answer with Content-Type: application/json.
endpoint returned empty response A 2xx with an empty body. The selector needs a JSON document to work on.
invalid query: ... The response_selector does not match the response shape (key on a non-object, index on a non-array, index out of bounds).

A separate validation error applies when a Knowledge Query upsert takes the resolver reference from an input parameter ("external_value": "$color") and the supplied value is neither a resolver name nor a GID:

HTTP 422
{
  "message": "Unprocessable Entity",
  "errors": [
    "input_params[color] is not valid external value, must be valid GID of external data resolver or config name"
  ]
}

Limits: all external lookups of one request must complete within 10 seconds, and a response body is read up to 10 MB. Slow or oversized endpoints fail the request.

Best Practices

Security

  • Store API keys and tokens securely; use environment variables in Terraform.
  • Use HTTPS endpoints only for external APIs.
  • Implement proper authentication on your external endpoints.

Performance

  • External resolver calls add latency to queries. Use sparingly for truly external data.
  • Consider caching on your external API if data doesn't change frequently.
  • Keep response payloads small; use response_selector to extract only needed data.

Design

  • Use descriptive resolver names that indicate the data source.
  • One resolver per data type/source for better maintainability.
  • Document the expected response format for each resolver.

Complete Example

See the full working example at: ContX IQ: Query External Data Sources via Data Resolver

Next Steps