Skip to content

Entity Resolution Check API

This page documents how to create and manage an Entity Resolution check via the REST API. The check uses the standard Quality Checks endpoints: set rule to entityResolution and configure the distinction field, the target fields, and the composite match threshold under properties. The rule derives its evaluated fields from target_fields, so the fields array is not used. All endpoints share the base URL of your deployment (for example, https://your-instance.qualytics.io/api) and require a Bearer token.

Tip

For complete API documentation, including request and response schemas, visit the API docs.

Permissions

All endpoints require the Member role plus a team permission on the check's datastore: Reporter to read, Author to create or update an Active check, archive, or delete, and Drafter to create or update a Draft. See Permissions for the full matrix.

Create

Endpoint: POST /api/quality-checks

Permission: Member role + Author team permission on the container's datastore (Drafter when status is "Draft").

Create an Entity Resolution check (every accepted field)

Company name, industry, and country are compared. Industry and country use exact comparison as weighted evidence, so they contribute to the score without preventing records from being compared.

{
    "description": "Similar company records must share a company_id",
    "rule": "entityResolution",
    "fields": [],
    "container_id": 145,
    "filter": null,
    "properties": {
        "distinct_field_name": "company_id",
        "composite_match_threshold": 0.85,
        "target_fields": [
            {
                "type": "String",
                "field_name": "company_name",
                "role": "compare",
                "comparison_type": "fuzzy",
                "pair_substrings": true,
                "pair_homophones": false,
                "consider_term_frequency": false,
                "weight": 4.0
            },
            {
                "type": "String",
                "field_name": "industry",
                "role": "compare",
                "comparison_type": "exact",
                "weight": 2.0
            },
            {
                "type": "String",
                "field_name": "country",
                "role": "compare",
                "comparison_type": "exact",
                "weight": 1.0
            }
        ]
    },
    "tags": ["master-data"],
    "additional_metadata": {"system": "crm"},
    "anomaly_message_field": null,
    "template_id": null,
    "status": "Active",
    "owner_id": 7,
    "default_anomaly_assignee_id": 12
}

Top-Level Field Notes

Field Required Notes
description Yes Free-text description shown in the UI.
rule Yes Must be "entityResolution".
fields Yes Send []. The list of evaluated fields is computed from properties.target_fields.
container_id Yes ID of the container (table or file) the check runs against.
filter No SQL WHERE expression. Applied before entity resolution runs, so only filtered rows are clustered. Send null for no filter.
properties.distinct_field_name Yes Name of the field that must hold a single value within each resolved entity cluster. Accepted field types: Integral, Fractional, Boolean, String, Date, and Timestamp.
properties.composite_match_threshold Yes Number between 0.0 and 1.0. Pairs whose weighted composite score is greater than or equal to this value are treated as matches. Default 0.7.
properties.target_fields Yes Non-empty array. Each entry has a data type, a field role, and a compatible comparison type. Comparison fields can also have a weight and type-specific settings.
tags No List of tag names applied to the check for filtering and organization.
additional_metadata No Free-form key-value pairs, typically links to governance records or other systems.
anomaly_message_field No Not applicable to Entity Resolution because the rule emits only Shape Anomalies. Send null.
template_id No ID of a Check Template to associate with the check. Send null when not using a template.
status No "Active" (default) or "Draft". Draft checks are not evaluated by Scans.
owner_id No ID of the user who owns the check. Defaults to the user creating the check.
default_anomaly_assignee_id No ID of the user automatically assigned to anomalies produced by the check.

Coverage is not supported

Entity Resolution does not accept a coverage value. A cluster is either compliant or non-compliant; there is no fractional tolerance to set.

Target Field Notes

Every entry in target_fields uses the same public structure:

Field Required Notes
type Yes "String", "Numeric", or "DateTime". This must agree with the source field: Numeric accepts Integral or Fractional fields, and DateTime accepts Date or Timestamp fields.
field_name Yes Name of the source field. A field can appear only once in target_fields.
role Yes "block" or "compare". A block field is an exact boundary that must agree before a pair can be compared. A compare field contributes evidence to the composite score.
comparison_type Yes A comparison supported by the selected type. Block fields must use "exact". Compare fields can also use "exact" as weighted binary evidence.
weight No Non-negative number controlling a compare field's contribution to the composite score. Default 1.0. When comparison fields are configured, at least one must have a positive weight. Weight is ignored for block fields.

For example, this block field creates a hard tenant boundary and does not need a weight:

{
    "type": "Numeric",
    "field_name": "tenant_id",
    "role": "block",
    "comparison_type": "exact"
}

String Fields

{
    "type": "String",
    "field_name": "full_name",
    "role": "compare",
    "comparison_type": "fuzzy",
    "pair_substrings": true,
    "pair_homophones": false,
    "consider_term_frequency": false,
    "weight": 1.0
}
Field Required Notes
comparison_type Yes "fuzzy" uses text similarity. "exact" scores 1.0 when the values agree and 0.0 when they differ.
pair_substrings No With fuzzy comparison, a pair where one string contains the other scores 1.0 on this field. Default false.
pair_homophones No With fuzzy comparison, a pair whose values sound alike scores 1.0 on this field. Default false.
consider_term_frequency No With fuzzy comparison, rare tokens carry more weight than common tokens. Default false.

Numeric Fields

{
    "type": "Numeric",
    "field_name": "annual_revenue",
    "role": "compare",
    "comparison_type": "relative",
    "offset": 0.05,
    "weight": 1.0
}
Field Required Notes
comparison_type Yes "exact" compares equality, "absolute" uses a fixed tolerance, and "relative" uses a percentage tolerance.
offset No Non-negative tolerance. With "absolute", the pair scores 1.0 when |a - b| <= offset. With "relative", use a fraction such as 0.05 for 5%. Default 0.0.

Datetime Fields

{
    "type": "DateTime",
    "field_name": "registered_at",
    "role": "compare",
    "comparison_type": "offset",
    "offset_seconds": 3600,
    "weight": 1.0
}
Field Required Notes
comparison_type Yes "exact" compares equality, "offset" allows a difference in seconds, and "granularity" compares values in the same time bucket.
offset_seconds No Non-negative tolerance in seconds for "offset". Default 0.
granularity No Bucket used by "granularity": "Day", "Week", "Month", or "Year". Omit it for other comparison types.

Migrated Checks and the Retired Payload

Checks saved before this structure existed were rewritten automatically, so a GET returns the current shape for every check:

  • A target field whose old match_type was exact became a block field with exact comparison, preserving its hard-boundary behavior.
  • Every other target field became a compare field, keeping its weight and its type-specific settings.
  • The comparison type carries over the old match_type value. Where a stored field had none, it takes the default for its type: "fuzzy" for String, "absolute" for Numeric, and "offset" for DateTime.

The old match_type property is rejected rather than translated. A POST or PUT that still sends it fails with 422 Unprocessable Entity and the message Target fields must use separate 'role' and 'comparison_type' properties, so a script written against the old payload fails loudly instead of saving something different from what it asked for.

Read

Endpoint: GET /api/quality-checks/{id}

Permission: Member role + Reporter team permission on the check's datastore.

Retrieve a check by ID
curl -X GET "https://your-instance.qualytics.io/api/quality-checks/101" \
  -H "Authorization: Bearer YOUR_TOKEN"

Update

Endpoint: PUT /api/quality-checks/{id}

Permission: Member role + Author team permission on the check's datastore (Drafter when the check stays Draft).

The PUT endpoint requires description; include the check's current description (or a new one) along with the fields you want to change. Send the whole properties object: partial property updates are not merged.

Raise the composite match threshold
curl -X PUT "https://your-instance.qualytics.io/api/quality-checks/101" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "Similar company records must share a company_id",
    "properties": {
        "distinct_field_name": "company_id",
        "composite_match_threshold": 0.9,
        "target_fields": [
            {"type": "String", "field_name": "company_name", "role": "compare", "comparison_type": "fuzzy", "weight": 4.0}
        ]
    }
  }'

A PUT can update most fields on an existing Entity Resolution check. Three values stay immutable: the rule type, the container the check is attached to, and the associated Check Template. If any of these needs to change, delete the check and create a new one.

Editable vs Immutable on PUT

Editable

  • description
  • filter
  • properties.distinct_field_name
  • properties.target_fields
  • properties.composite_match_threshold
  • tags
  • additional_metadata
  • status
  • owner_id
  • default_anomaly_assignee_id

Immutable

  • rule
  • container_id
  • template_id

Delete

Endpoint: DELETE /api/quality-checks/{id}

Permission: Member role + Author team permission on the check's datastore.

Three query parameters control what happens, mirroring the two-stage flow in the UI:

Parameter Default Description
archive true When true, the check is archived and can be restored later. When false, the check is permanently deleted.
status Discarded The archive resolution: Discarded (no longer relevant) or Invalid (wrong and suppressed from future AI generation). Only used when archive=true.
delete_anomalies true Whether to permanently delete the anomalies the check produced. A permanent delete (archive=false) always removes them.
Archive a check, keeping its anomalies
curl -X DELETE "https://your-instance.qualytics.io/api/quality-checks/101?archive=true&status=Discarded&delete_anomalies=false" \
  -H "Authorization: Bearer YOUR_TOKEN"
Delete a check permanently
curl -X DELETE "https://your-instance.qualytics.io/api/quality-checks/101?archive=false" \
  -H "Authorization: Bearer YOUR_TOKEN"

Error responses

Status Description
400 Bad Request A business-logic conflict prevents the operation (for example, changing properties while the check is archived, or switching an archived check between Discarded and Invalid).
401 Unauthorized Missing or invalid API token.
403 Forbidden The caller lacks the required team permission on the check's datastore.
404 Not Found The check ID does not exist.
422 Unprocessable Entity The payload fails schema validation (for example, description missing on PUT, or target_fields is empty or a role is not recognized).

Target Field Validation Messages

A 422 raised by target_fields names the field it rejected, so the response says what to fix without guessing. The same checks run behind the Validate button in the UI, which is why the message you see there matches the one the API returns.

{field} below is the field name in quotes. When an entry carries no field name at all, some of these messages identify it by its place in the array instead, as at position N.

Message What It Means
Target fields must use separate 'role' and 'comparison_type' properties The entry still uses the retired match_type property. Send role and comparison_type instead.
Target fields must use public 'type' values instead of internal serialization properties The entry carries a type marker copied from a response returned before this structure existed, instead of the public type. Send type as String, Numeric, or DateTime.
Target field {field} has an unknown type '...' type is not String, Numeric, or DateTime.
Target field {field} is configured more than once The same source field appears twice in target_fields.
Target field {field} has invalid role '...' role is not block or compare.
Target field {field} has invalid comparison_type '...' The comparison is not one the field's type supports.
Blocking target field {field} must use exact comparison A block field was given a comparison other than exact.
Comparison target field {field} must have a non-negative weight A compare field carries a negative weight, or null.
At least one comparison target field must have a positive weight Every compare field has a weight of 0, so nothing could contribute to the composite score.
Target field {field} does not exist in the container The field name does not match any field on the container.
Target field {field} is ..., but type ... requires one of: ... The declared type disagrees with the source field. Numeric needs an Integral or Fractional field, and DateTime needs a Date or Timestamp field.