What is Trust Score?
Trust Score is IndyKite's data quality assessment system. It evaluates how trustworthy your data is based on configurable dimensions like freshness, origin, and verification status.
Key benefits:
- Data quality visibility: Know which data is fresh, verified, and complete.
- Risk-based decisions: Use trust scores in authorization policies (KBAC) and queries (ContX IQ).
- Automated recalculation: Scores update on a schedule as data ages or changes.
- Flexible weighting: Prioritize the dimensions that matter for your use case.
How does it work?
Trust Score involves three components:
- Trust Score Profile: Configuration defining which dimensions to evaluate and their weights.
- Node metadata: Properties on nodes that provide input for scoring (e.g.,
source,verified_time). - _TrustScore node: Automatically created in the IKG, linked to the scored node via
_HASrelationship.
Scoring flow
- Create a Trust Score Profile specifying dimensions and weights
- Ingest nodes with metadata (source, verified_time, etc.)
- IndyKite calculates scores based on the configured schedule
- A
_TrustScorenode is created/updated for each scored node - Query trust scores via ContX IQ or use in KBAC policies
What credentials do I need?
- Creating profiles: Service Account credentials (Config API)
- Ingesting nodes with metadata: AppAgent credentials (Capture API)
- Querying trust scores: AppAgent credentials + optional user access token (ContX IQ)
Configuration methods:
- Terraform: indykite_trust_score_profile resource
- REST API: Config API documentation
Trust Score Dimensions
Five dimensions can be used to evaluate data quality:
| Dimension | Description | Input metadata |
FRESHNESS |
How recent is the data? Older data scores lower. | Property update timestamps |
ORIGIN |
Where did the data come from? Trusted sources score higher. | source metadata on properties |
VALIDITY |
Does the data comply with expected formats and rules? | Format validation results |
COMPLETENESS |
Are all critical fields present? | Presence of required properties |
VERIFICATION |
Has the data been verified/confirmed? | verified_time metadata |
Each dimension has a weight (0-1). The weighted combination produces the overall trust score.
Scoring rules
- Each dimension produces a per-node score between 0 and 1. The final score (
_final_score) is also between 0 and 1; a higher value means a higher weighted aggregate across the profile's active dimensions; one low-weight dimension can score poorly while the final score stays high. - A
weightof0disables the dimension: it is not computed and does not appear on the_TrustScorenode. A profile must contain at least one dimension with a non-zero weight; the Config API rejects it otherwise. - A dimension's
namecannot be changed once set; itsweightcan. - One
node_classificationmay have several profiles, each reflecting a different notion of trust. Every profile writes its own_TrustScorenode per scored node, identified by the profile name in_ingress. - Scoring runs only on the configured
schedule. There is no endpoint to trigger a run, and results are not available until the first scheduled run completes; on large graphs that can take a while.last_run_start_timeandlast_run_end_timeon the profile show when it last ran.
Schedule Options
Trust scores are recalculated periodically based on the schedule:
| Schedule Value | Description |
THREE_HOURS |
Recalculate every 3 hours |
SIX_HOURS |
Recalculate every 6 hours |
TWELVE_HOURS |
Recalculate every 12 hours |
DAILY |
Recalculate once per day |
Trust Score Profile Configuration
REST API Endpoints
| Operation | Method | Endpoint |
| Create | POST | /configs/v1/trust-score-profiles |
| Read by ID | GET | /configs/v1/trust-score-profiles/{id} |
| Read by name | GET | /configs/v1/trust-score-profiles/{name}?location={project_id} |
| List all | GET | /configs/v1/trust-score-profiles?project_id={id} |
| Update | PUT | /configs/v1/trust-score-profiles/{id} |
| Delete | DELETE | /configs/v1/trust-score-profiles/{id} |
Create Request Syntax
{
"project_id": "<string>",
"name": "<string>",
"display_name": "<string>",
"description": "<string>",
"node_classification": "<string>",
"schedule": "<string>",
"dimensions": [
{
"name": "<string>",
"weight": <number>
}
]
}
What does each field mean?
Required fields
project_id: The GID of the project where the profile will be created.name: Unique, immutable identifier. Must start with a lowercase letter, contain only lowercase letters, numbers, and hyphens.node_classification: The node type (label) to score. Must be PascalCase (e.g.,Person,Organization,Asset).schedule: How often to recalculate scores. See schedule options above.dimensions: Array of at least one dimension with name and weight.
Optional fields
display_name: Human-readable name (can be updated).description: Description of the profile (max 65000 UTF-8 bytes).
Dimension object
name: One ofFRESHNESS,ORIGIN,VALIDITY,COMPLETENESS,VERIFICATION.weight: A number between 0 and 1 indicating the importance of this dimension.
Example: Create Trust Score Profile
POST /configs/v1/trust-score-profiles{
"project_id": "gid:AAAABbbbCCCC...",
"name": "person-trust-profile",
"display_name": "Person Trust Score",
"description": "Evaluates trustworthiness of Person nodes",
"node_classification": "Person",
"schedule": "TWELVE_HOURS",
"dimensions": [
{
"name": "FRESHNESS",
"weight": 0.3
},
{
"name": "ORIGIN",
"weight": 0.4
},
{
"name": "VERIFICATION",
"weight": 0.3
}
]
}
Example: Update Trust Score Profile
PUT /configs/v1/trust-score-profiles/{id}{
"display_name": "Updated Person Trust Score",
"schedule": "THREE_HOURS",
"dimensions": [
{
"name": "FRESHNESS",
"weight": 1
},
{
"name": "ORIGIN",
"weight": 1
}
]
}
Read Response
Reading a profile (GET /configs/v1/trust-score-profiles/{id}) returns its configuration plus execution metadata, including the last-run details. (On the List endpoint, pass full_fetch=true to get full objects instead of just metadata.)
{
"id": "gid:AAAABbbbCCCC...",
"name": "person-trust-profile",
"display_name": "Person Trust Score",
"description": "Evaluates trustworthiness of Person nodes",
"node_classification": "Person",
"schedule": "TWELVE_HOURS",
"dimensions": [
{
"name": "FRESHNESS",
"weight": 0.3
}
],
"organization_id": "gid:...",
"project_id": "gid:...",
"create_time": "2024-01-15T10:30:00Z",
"update_time": "2024-01-15T10:30:00Z",
"created_by": "gid:...",
"updated_by": "gid:...",
"last_run_id": "gid:...",
"last_run_start_time": "2024-01-15T12:00:00Z",
"last_run_end_time": "2024-01-15T12:00:05Z",
"dimensions_execution_times": {...}
}
Run metadata fields
last_run_id, last_run_start_time, last_run_end_time, and dimensions_execution_times are output-only and describe the last successful run. dimensions_execution_times is keyed by the lowercase dimension name and holds the window of each dimension's computation:
"dimensions_execution_times": {
"freshness": {
"execution_start_time": "2024-01-15T12:00:00Z",
"execution_end_time": "2024-01-15T12:00:02Z"
},
"origin": {
"execution_start_time": "2024-01-15T12:00:00Z",
"execution_end_time": "2024-01-15T12:00:03Z"
},
"verification": {
"execution_start_time": "2024-01-15T12:00:00Z",
"execution_end_time": "2024-01-15T12:00:05Z"
}
}
Field constraints (REST)
| Field | Constraint |
node_classification |
Pattern ^([A-Z][a-z]+)+$. Immutable; may be omitted on PUT. |
dimensions |
Array of 1 or more {name, weight}. On PUT, omitting it keeps the current list; providing it replaces the whole list. |
dimensions[].weight |
Number between 0 and 1 inclusive. |
display_name |
2-254 characters. Equals name when not set. On PUT, omitted or null keeps it; an empty string removes it. |
description |
2-65000 characters. Same PUT semantics as display_name. |
If-Match header |
Carries the ETag on PUT and DELETE; a stale value returns 412. |
| List query parameters | project_id (required), full_fetch, and search (substring match on name, display name, or description). |
| Read by name | GET /configs/v1/trust-score-profiles/{name}?location={project_id}; an optional version parameter reads an earlier configuration version. |
Ingesting Nodes with Metadata
For trust scoring to work, nodes must have metadata on their properties. Use the Capture API to ingest nodes with metadata.
Metadata fields for Trust Score
| Metadata field | Used by dimension | Description |
source |
ORIGIN | Where the data came from (e.g., "passport", "HR_System") |
verified_time |
VERIFICATION, FRESHNESS | When the data was last verified (ISO 8601 timestamp) |
assurance_level |
VERIFICATION | Confidence level (numeric) |
What each dimension reads
| Dimension | Input | Who sets it |
FRESHNESS |
The most recent update_time across the node's properties, measured against the time of the run |
The platform, on every Capture upsert of the property |
VERIFICATION |
The verified_time metadata of the node's properties |
You, in the property metadata |
ORIGIN |
The source metadata of the node's properties |
You, in the property metadata |
COMPLETENESS |
Which property types are present on the node | You, by the properties you ingest |
VALIDITY |
The property types and their values | You, by the properties you ingest |
A node with no properties at all cannot be scored on FRESHNESS, VERIFICATION, or ORIGIN.
Capture API Request with Metadata
POST /capture/v1/nodes{
"nodes": [
{
"external_id": "jane-doe",
"type": "Person",
"is_identity": true,
"properties": [
{
"type": "name",
"value": "Jane Doe",
"metadata": {
"source": "passport",
"verified_time": "2024-01-10T14:30:00Z"
}
},
{
"type": "email",
"value": "jane@example.com",
"metadata": {
"source": "self_reported",
"verified_time": "2024-01-08T09:00:00Z"
}
},
{
"type": "passport_id",
"value": "A67897XYZ",
"metadata": {
"source": "passport",
"assurance_level": 3,
"verified_time": "2024-01-10T14:30:00Z"
}
}
]
}
]
}
Properties with source: "passport" and recent verified_time will score higher than self-reported data with older timestamps.
Trust Score in the IKG
After the trust score profile runs, a _TrustScore node is created and linked to each scored node:
(Person:jane-doe)-[:_HAS]->(_TrustScore)
The _TrustScore node contains:
- Overall trust score value
- Individual dimension scores
- Calculation timestamp
_TrustScore node properties
| Property | Meaning |
_ingress |
The name of the Trust Score Profile that produced this node. One _TrustScore node exists per scored node and profile. |
_final_score |
The weighted overall score, 0 to 1. |
_freshness, _completeness, _validity, _origin, _verification |
Per-dimension scores, 0 to 1. Only dimensions with a non-zero weight in the profile are present. |
_<dimension>_explanation |
A human-readable explanation of that dimension's score, e.g. _verification_explanation. Derived by ContX IQ when the score node is read; it is not a stored property you can request by name. |
_TrustScore is reserved. The Capture API rejects it as a node type (it fails the node type pattern ^(?:[A-Z][a-z]+)+$) and as a property type, and ContX IQ policies cannot upsert or delete it. Deleting the profile removes its score nodes.
Querying Trust Scores with ContX IQ
Use ContX IQ to query trust scores and use them in authorization decisions.
Important: _TrustScore is an internal node label that CIQ cypher is not allowed to match directly. Instead, read the score through the virtual trust_score accessor on the scored node: <var>.trust_score._final_score for the overall score, or <var>.trust_score.<dimension> / <var>.trust_score.* for individual dimensions.
CIQ Policy including Trust Score
{
"meta": {
"policy_version": "1.0-ciq"
},
"subject": {
"type": "Person"
},
"condition": {
"cypher": "MATCH (subject:Person)"
},
"allowed_reads": {
"nodes": [
"subject.property.*",
"subject.trust_score._final_score",
"subject.trust_score.*"
]
}
}
Knowledge Query for Trust Score
{
"nodes": [
"subject.property.name",
"subject.property.email",
"subject.trust_score._final_score"
]
}
Accessor rules
<var>.trust_scorereturns the whole_TrustScorenode,<var>.trust_score.<property>one of its properties (_final_score,_ingress,_freshness,_completeness,_validity,_origin,_verification), and<var>.trust_score.*all of them. In the execute response the keys are spelled the same way, e.g."person.trust_score._verification": 0.83.- When several profiles score the same node type, the accessor yields one row per
_TrustScorenode. Select a profile by filtering on<var>.trust_score._ingresswith the profilename. - The accessor works in policy filters and Knowledge Query filters as a comparison attribute, e.g.
{"attribute": "person.trust_score._final_score", "operator": ">=", "value": 0.66}. A node without a score for the matched profile has no value, so such a comparison excludes it. - Explanations are only delivered when the whole node or the wildcard is read; a Knowledge Query that lists
<var>.trust_score._explanationis rejected when it is saved. - Naming
_TrustScoreas a label in the policy Cypher is rejected withinvalid node type `_TrustScore` in cypher for node <var>. Listing<var>.trust_score...underallowed_upserts.nodes.existing_nodesorallowed_deletes.nodesis rejected as well; scores are read-only.
Using Trust Score in KBAC
Trust scores can be used in KBAC policies to make authorization decisions based on data quality. As in CIQ, do not match the _TrustScore node in cypher - read the score through the subject.trust_score._final_score accessor in the condition filter:
{
"meta": {
"policy_version": "2.0-kbac"
},
"subject": {
"type": "Person"
},
"actions": [
"CAN_ACCESS"
],
"resource": {
"type": "Document"
},
"condition": {
"cypher": "MATCH (subject:Person) MATCH (resource:Document)",
"filter": {
"operator": ">",
"attribute": "subject.trust_score._final_score",
"value": 0.8
}
}
}
This policy only allows access if the person's _final_score exceeds 0.8. The same policy is also valid with "policy_version": "3.0-kbac" - the raw-Cypher version used for data-residency routing (see the data residency guide).
Trust score in the Cypher condition
A KBAC policy's condition.filter is evaluated against the request's context.input_params and the token claims ($param, $token.claim). It does not read graph data, so a score threshold that must be checked in the graph goes into condition.cypher. The score node hangs off the subject through _HAS, and _ingress pins the profile. The _TrustScore label restriction described under Accessor rules applies to ContX IQ policy Cypher only; KBAC condition Cypher is validated by the graph database and may match the label:
{
"meta": {
"policy_version": "2.0-kbac"
},
"subject": {
"type": "Person"
},
"actions": [
"CAN_ACCESS"
],
"resource": {
"type": "Document"
},
"condition": {
"cypher": "MATCH (subject:Person)-[:_HAS]->(t:_TrustScore) MATCH (resource:Document) WHERE t._ingress = 'person-trust-profile' AND t._final_score > 0.8"
}
}
Per-dimension properties can be combined in the same WHERE clause, for example t._final_score >= 0.9 AND t._verification > 0.8. Because scores are recomputed only on the profile schedule, choose a schedule that matches how current the decision needs to be.
Terraform Configuration
Use the indykite_trust_score_profile resource:
resource "indykite_trust_score_profile" "person_trust" {
location = indykite_application_space.my_app_space.id
name = "person-trust-profile"
display_name = "Person Trust Score"
description = "Evaluates trustworthiness of Person nodes"
node_classification = "Person"
schedule = "UPDATE_FREQUENCY_TWELVE_HOURS"
dimension {
name = "NAME_FRESHNESS"
weight = 0.3
}
dimension {
name = "NAME_ORIGIN"
weight = 0.4
}
dimension {
name = "NAME_VERIFICATION"
weight = 0.3
}
}
Terraform Arguments Reference
| Argument | Required | Description |
location |
Yes | Application Space ID where the profile is created |
name |
Yes | Unique, immutable identifier |
node_classification |
Yes | Node type to score (PascalCase) |
schedule |
Yes | Recalculation frequency |
dimension |
Yes | At least one dimension block |
display_name |
No | Human-readable name |
description |
No | Profile description |
Terraform Schedule Values
UPDATE_FREQUENCY_THREE_HOURSUPDATE_FREQUENCY_SIX_HOURSUPDATE_FREQUENCY_TWELVE_HOURSUPDATE_FREQUENCY_DAILY
Terraform Dimension Names
NAME_FRESHNESSNAME_ORIGINNAME_VALIDITYNAME_COMPLETENESSNAME_VERIFICATION
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 | Profile ID/name doesn't exist |
| 412 | Precondition Failed | ETag mismatch (concurrent modification) |
| 422 | Unprocessable Entity | Validation error (invalid name, missing dimensions, etc.) |
Common validation errors
invalid field name: is not valid name- Name must start with lowercase letter, contain only lowercase letters, numbers, and hyphens.invalid field project_id: identifier is not of PROJECT- Must use a valid project GID.missing field node_classification- Node type is required.missing field schedule- Schedule is required.missing field dimensions- At least one dimension is required.
Best Practices
Dimension weighting
- Weight dimensions based on your use case requirements.
- For identity verification: prioritize
VERIFICATIONandORIGIN. - For real-time data: prioritize
FRESHNESS. - For compliance: prioritize
COMPLETENESSandVALIDITY.
Schedule selection
- Use
THREE_HOURSfor data that changes frequently and freshness is critical. - Use
DAILYfor stable data where frequent recalculation adds overhead. TWELVE_HOURSis a good default for most use cases.
Metadata ingestion
- Always include
sourcemetadata to enable ORIGIN scoring. - Update
verified_timewhen data is re-verified. - Use consistent source names across your data pipeline.
Next Steps
- ContX IQ guide: ContX IQ Guide
- KBAC guide: Dynamic Authorization Guide
- Terraform provider: Trust Score Profile Resource
- Credentials guide: Credentials Guide