Back to all guides
Trust Score

Trust Score: Assess Data Trustworthiness

How to configure Trust Score Profiles to assess data trustworthiness based on freshness, origin, validity, completeness, and verification.

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 _HAS relationship.

Scoring flow

  1. Create a Trust Score Profile specifying dimensions and weights
  2. Ingest nodes with metadata (source, verified_time, etc.)
  3. IndyKite calculates scores based on the configured schedule
  4. A _TrustScore node is created/updated for each scored node
  5. 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:

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 weight of 0 disables the dimension: it is not computed and does not appear on the _TrustScore node. A profile must contain at least one dimension with a non-zero weight; the Config API rejects it otherwise.
  • A dimension's name cannot be changed once set; its weight can.
  • One node_classification may have several profiles, each reflecting a different notion of trust. Every profile writes its own _TrustScore node 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_time and last_run_end_time on 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 of FRESHNESS, 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_score returns the whole _TrustScore node, <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 _TrustScore node. Select a profile by filtering on <var>.trust_score._ingress with the profile name.
  • 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._explanation is rejected when it is saved.
  • Naming _TrustScore as a label in the policy Cypher is rejected with invalid node type `_TrustScore` in cypher for node <var>. Listing <var>.trust_score... under allowed_upserts.nodes.existing_nodes or allowed_deletes.nodes is 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_HOURS
  • UPDATE_FREQUENCY_SIX_HOURS
  • UPDATE_FREQUENCY_TWELVE_HOURS
  • UPDATE_FREQUENCY_DAILY

Terraform Dimension Names

  • NAME_FRESHNESS
  • NAME_ORIGIN
  • NAME_VALIDITY
  • NAME_COMPLETENESS
  • NAME_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 VERIFICATION and ORIGIN.
  • For real-time data: prioritize FRESHNESS.
  • For compliance: prioritize COMPLETENESS and VALIDITY.

Schedule selection

  • Use THREE_HOURS for data that changes frequently and freshness is critical.
  • Use DAILY for stable data where frequent recalculation adds overhead.
  • TWELVE_HOURS is a good default for most use cases.

Metadata ingestion

  • Always include source metadata to enable ORIGIN scoring.
  • Update verified_time when data is re-verified.
  • Use consistent source names across your data pipeline.

Next Steps