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:
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_typewasexactbecame 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_typevalue. 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
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
descriptionfilterproperties.distinct_field_nameproperties.target_fieldsproperties.composite_match_thresholdtagsadditional_metadatastatusowner_iddefault_anomaly_assignee_id
Immutable
rulecontainer_idtemplate_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
Delete a check permanently
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. |