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_valuethat 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-kbacpolicy condition touches one, the platform calls the configured resolver and substitutes the fetched value. A3.0-kbacpolicy runs its Cypher unmodified and cannot resolve data references; creating one that references a resolver-backed property fails withexternal 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:
- User (Alice) requests vehicle details including VIN
- ContX IQ policy authorizes: Alice can READ vehicles covered by her contracts
- Query fetches category from IKG: "Car"
- Query detects data reference (
external_value) on vin property - Resolver calls external API to fetch VIN
- 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:
- Terraform: indykite_external_data_resolver resource
- REST API: Config API documentation
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 onlyJSONis supported.response_content_type: Content type for responses. Currently onlyJSONis 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 thevehicle_idinput 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}orhttps://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, andboolinput 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
nullare 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].someValue | 123 |
.obj.secondLevel[2].someValue[1] | "value1.1" |
.obj.secondLevel[3].someValue | null |
.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:
categoryhas a regularvaluestored in the IKG.vinusesexternal_valuepointing 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_selectorto 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
- ContX IQ guide: ContX IQ Guide
- Terraform provider: External Data Resolver Resource
- REST API reference: Config API
- Credentials guide: Credentials Guide