Entity Resolution Recipe API
The Entity Resolution recipe has no API resource of its own. It is a guided flow over the same objects you can create by hand. Reproducing it programmatically means calling the standard endpoints in the order the recipe does: create the check, dry-run it, activate it, scan, materialize the golden set, and acknowledge the anomaly. The recipe's AI suggestions come from a small set of assist endpoints that you can call too.
Tip
For complete API documentation, including request/response schemas, visit the API docs.
All endpoints use the base URL of your Qualytics deployment (e.g., https://your-instance.qualytics.io/api) and require a Personal API Token. Every call needs the Member role. The team permission on the source datastore depends on the step, from Reporter for the AI assists to Editor for the scan and the materialize. See Permissions for the full matrix.
Note
The full payload reference for an Entity Resolution check, including every option of target_fields, is on the rule type's API page. This page shows only what the recipe sends.
The Recipe Sequence
1. Create the Check as a Draft
The Review step creates the check in Draft status. type takes String, Numeric, or DateTime; role is compare for weighted evidence and block for a hard boundary, and block fields always use exact comparison. weight is any non-negative number, and at least one compare field must have a positive weight. The recipe normalizes its suggested weights to sum to 1, but the API does not require that.
The recipe starts the threshold at 0.75, which is why the example sends it explicitly. A check created by hand without the field uses the platform default of 0.7.
Endpoint: POST /quality-checks
Permission: Drafter team permission on the datastore for a Draft, Author to create it Active directly
Example request
curl -X POST "https://your-instance.qualytics.io/api/quality-checks" \
-H "Authorization: Bearer YOUR_QUALYTICS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"description": "Entity Resolution check on customers",
"container_id": 145,
"rule": "entityResolution",
"status": "Draft",
"properties": {
"distinct_field_name": "customer_id",
"composite_match_threshold": 0.75,
"target_fields": [
{
"type": "String",
"field_name": "company_name",
"role": "compare",
"comparison_type": "fuzzy",
"pair_substrings": true,
"pair_homophones": true,
"consider_term_frequency": false,
"weight": 0.5
},
{
"type": "String",
"field_name": "billing_email",
"role": "compare",
"comparison_type": "fuzzy",
"weight": 0.3
},
{
"type": "String",
"field_name": "country",
"role": "compare",
"comparison_type": "exact",
"weight": 0.2
}
]
}
}'
Response (200 OK): the created check, with id and status: "Draft".
Before creating, the Review step also asks whether an active Entity Resolution check already exists for the asset. The same probe is available as POST /quality-checks/find-conflicting. It takes {"quality_check": <the create payload>}, optionally with exclude_check_id to ignore a check you are editing, needs the Drafter team permission, and responds with conflicting_check set to the overlapping check or to null.
2. Dry-Run the Check
The Validate step runs the check on a sample of up to 10,000 records per partition.
Endpoint: POST /quality-checks/{id}/dry-run
Permission: Drafter team permission on the datastore
Example request
curl -X POST "https://your-instance.qualytics.io/api/quality-checks/812/dry-run" \
-H "Authorization: Bearer YOUR_QUALYTICS_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "max_records_analyzed_per_partition": 10000 }'
Response (200 OK): a list with one result for the table, carrying the records processed and a per-partition breakdown. When duplicates exist, each partition result carries a single anomaly whose record count is the number of duplicate records in that sample.
3. Activate the Check
Validate finishes by setting the check to Active. The bulk update endpoint takes a list, so several checks can be activated at once.
Endpoint: PATCH /quality-checks
Permission: Author team permission on the datastore
Example request
curl -X PATCH "https://your-instance.qualytics.io/api/quality-checks" \
-H "Authorization: Bearer YOUR_QUALYTICS_TOKEN" \
-H "Content-Type: application/json" \
-d '[{ "id": 812, "status": "Active" }]'
Response (200 OK): the updated checks.
PATCH /quality-checks/activate with [{ "id": 812 }] is a more forgiving alternative: it activates every check it can and reports the ones it could not, with the reason for each, instead of failing the whole request.
4. Run the Full Scan
The Scan step runs a full scan of every record in the selected table, not only the records changed since the last scan.
Endpoint: POST /operations/run
Permission: Editor team permission on the datastore
Example request
curl -X POST "https://your-instance.qualytics.io/api/operations/run" \
-H "Authorization: Bearer YOUR_QUALYTICS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "scan",
"datastore_id": 12,
"container_names": ["customers"],
"incremental": false
}'
Response (200 OK): the queued scan operation, with its id. Poll GET /operations/{id} until result is no longer queued or running.
The recipe also raises enrichment_source_record_limit so that up to 100,000 source records of the duplicates are stored for remediation, and sets archive_duplicate_anomalies to false so the new anomaly does not archive the previous run's. Once the scan completes, list the check's anomalies with GET /anomalies?quality_check={check_id} and read the duplicates with GET /anomalies/{id}/source-record. Each source record carries the entity it was grouped into in the _qualytics_entity_id column.
5. Materialize the Golden Set
The Materialize step writes the records you kept to the enrichment destination. The records to leave out travel as the list of their distinction-field values in exclusion_values, and materialize_inverse asks for the companion output of excluded records.
Endpoint: POST /operations/run
Permission: Editor team permission on the datastore
| Field | Required | Description |
|---|---|---|
type |
Yes | "materialize". |
datastore_id |
Yes | The source datastore. The outputs are written to its linked enrichment destination. |
container_names |
Yes, for this use | The source table, as a one-element list. The endpoint also accepts container_tags instead. |
exclusion_field |
With exclusion_values |
The field whose values identify the records to exclude. The recipe uses the distinction field. |
exclusion_values |
With exclusion_field |
The values of the excluded records, as strings. At most 25,000 values, and 1,000,000 characters in total. Duplicates are ignored. |
materialize_inverse |
No | true also writes the excluded records to an output named like the golden set with an _inverse suffix. Default false. |
exclusion_filter |
No | Alternative to the two fields above: a SQL condition over the table (a WHERE clause without the keyword), up to 4,000 characters, where rows that satisfy it are kept. Only comparison and logical operators are accepted. Cannot be combined with exclusion_field and exclusion_values. |
Example request
curl -X POST "https://your-instance.qualytics.io/api/operations/run" \
-H "Authorization: Bearer YOUR_QUALYTICS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "materialize",
"datastore_id": 12,
"container_names": ["customers"],
"exclusion_field": "customer_id",
"exclusion_values": ["CUST-2417", "CUST-3090", "CUST-5561"],
"materialize_inverse": true
}'
Response (200 OK): the queued materialize operation. When it completes, the enrichment destination holds <prefix>_mat_er_customers and, with materialize_inverse, <prefix>_mat_er_customers_inverse.
Requests that break the exclusion rules are rejected with 422 Unprocessable Entity. The message arrives inside the standard validation error list, prefixed with Value error,, and reads one of:
exclusion_filter and exclusion_field/exclusion_values are mutually exclusiveexclusion_field is required when exclusion_values is setexclusion_values is required when exclusion_field is setexclusion_values exceeds the maximum of 25000 valuesexclusion_values exceeds the maximum total length of 1000000 charactersexclusion_filter exceeds the maximum length of 4000 charactersexclusion_filter may not call the '<name>' function; only comparison and logical predicates are permitted
A materialize on a datastore without a linked enrichment destination, or with a disconnected one, is rejected with 409 Conflict.
6. Acknowledge the Anomaly
After the golden set is written, the recipe acknowledges the anomaly whose duplicates it remediated.
Endpoint: PUT /anomalies/{id}
Permission: Author team permission on the datastore
Example request
curl -X PUT "https://your-instance.qualytics.io/api/anomalies/4021" \
-H "Authorization: Bearer YOUR_QUALYTICS_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "status": "Acknowledged" }'
Response (200 OK): the updated anomaly.
AI Assist Endpoints
The recipe's AgentQ suggestions come from six endpoints under /recipes/entity-resolution/. Each call stands alone: it takes the identifiers of the objects involved, asks the configured AI provider, and returns the suggestion with its reasoning. They all need the Member role, the Reporter team permission or above on the datastore of the container, check, or operation named in the request, and an AI integration configured for AgentQ. The AgentQ data sharing level must allow the step: Metadata Shared for all of them except golden-record recommendations, which need Source Data Shared.
| Endpoint | Request | Response |
|---|---|---|
POST /recipes/entity-resolution/suggest-entity-key |
container_id |
suggested_field_name, suggested_field_id, reasoning |
POST /recipes/entity-resolution/suggest-block-fields |
container_id, entity_key_field_id, optional selected_field_ids to leave out |
suggested_fields (each with field_name, field_id, type, role, comparison_type, weight, the three string options, and reasoning), composite_match_threshold, threshold_reasoning. Every block field comes back with exact comparison and weight 0. |
POST /recipes/entity-resolution/suggest-compare-fields |
Same as above; the recipe passes the blocking fields as selected_field_ids |
Same shape. The string options are pair_substrings, pair_homophones, and consider_term_frequency, and the weights are normalized to sum to 1. |
POST /recipes/entity-resolution/analyze-dry-run |
quality_check_id, plus records_processed, duplicate_entity_count, duplicate_record_count from a dry run; when any of the three counts is omitted, the endpoint runs the dry run itself |
assessment (good, adjust_threshold, adjust_fields, or no_matches), reasoning, suggested_threshold, and the three counts |
POST /recipes/entity-resolution/interpret-scan-results |
operation_id, quality_check_id |
headline, findings, recommended_next_steps |
POST /recipes/entity-resolution/recommend-golden-records |
container_id, quality_check_id, entity_clusters (each with entity_id and its records) |
recommendations, each with entity_id, preferred_record_index, reasoning |
Example: recommend golden records
curl -X POST "https://your-instance.qualytics.io/api/recipes/entity-resolution/recommend-golden-records" \
-H "Authorization: Bearer YOUR_QUALYTICS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"container_id": 145,
"quality_check_id": 812,
"entity_clusters": [
{
"entity_id": 7,
"records": [
{ "customer_id": "CUST-0182", "company_name": "Acme Corp", "billing_email": "billing@acme.com", "country": "BR" },
{ "customer_id": "CUST-2417", "company_name": "Acme Corp Ltda", "billing_email": "billing@acmecorp.com", "country": "BR" }
]
}
]
}'
Response (200 OK):
The records in one request may total at most 50 across all clusters. Larger sets are sent in several requests, which is how the recipe scores entities a few at a time. Entities the model could not score within the time allowed are left out of the response rather than failing the request, so a partial response is normal. Only when no entity could be scored does the request fail with 503. This endpoint sends the record values themselves to the AI provider; with no AI integration configured it fails and nothing leaves Qualytics.
Error Responses
| Status Code | Description |
|---|---|
400 Bad Request |
No AI integration is configured for the assist endpoints, or the anomaly cannot be changed, for example because it is archived. |
401 Unauthorized |
Missing or invalid API token. |
403 Forbidden |
The user lacks the role or the team permission the step needs, or the AgentQ data sharing level does not allow the assist. The message reads, for example, Golden-record recommendations needs Source Data Shared, and your Qualytics Administrator has not enabled it. |
404 Not Found |
No container, check, operation, or anomaly exists with the specified ID. |
409 Conflict |
The materialize cannot run because the datastore has no linked enrichment destination or that destination is disconnected. A scan returns it only when the datastore has no destination and its Remediation Strategy setting is anything other than None. |
422 Unprocessable Entity |
Invalid request body, such as a target_fields entry with a wrong type or role, an exclusion payload that breaks the rules above, a container with no profiled fields, or more than 50 records in one golden-record request. For scan interpretation, also an operation that is not a scan or that did not assert the given check. |
429 Too Many Requests |
Too many concurrent operations. Please try again later |
503 Service Unavailable |
The AI provider could not produce a usable response: AI assist could not produce a usable response. Please try again. |
Error response examples
403 Forbidden (data sharing level too low):
{ "detail": "Golden-record recommendations needs Source Data Shared, and your Qualytics Administrator has not enabled it." }
422 Unprocessable Entity (no profiled fields):
422 Unprocessable Entity (too many records in one request):
Related
- Entity Resolution API: the full payload and field notes for the check itself.
- Permissions: the roles and team permissions behind each step.
- How It Works: what the run produces and how the outputs are named.
- AgentQ Access Controls: the data sharing levels that gate the assist endpoints.