Checks API¶
This page documents the endpoints that apply to every check regardless of its rule type: the change log of a check, its definition at a point in time, and the daily assertion history behind its Insights section. Create, read, update, and delete payloads are rule-specific and live on each rule type's API page (see Getting Started with Checks). The optional impact_metrics field of those payloads is described in Impact Metrics in the API.
All endpoints use the base URL of your Qualytics deployment (for example, https://your-instance.qualytics.io/api) and require a Bearer token. Every timestamp is returned in UTC as ISO 8601 with a trailing Z; see Timestamps.
Tip
For complete API documentation, including request/response schemas, visit the API docs.
Permissions
All three endpoints require the Member role and the Reporter team permission on the check's datastore.
History¶
Returns the paginated change log of a check: one entry per saved version, with the fields that changed, their before and after values, the user who made the change, and when. Use it to reconstruct every state a check has been in.
Endpoint: GET /api/quality-checks/{id}/history
Query parameters:
| Parameter | Type | Description |
|---|---|---|
page |
int (default 1) |
Page number to return, starting at 1. |
size |
int (default 50, max 100) |
Number of entries per page. |
include_deleted |
bool (default false) |
When true, also returns the history of a deleted check. |
Example request
Example response (abbreviated)
{
"items": [
{
"changeset": {
"filter": ["order_date >= '2026-01-01'", "order_date >= '2026-06-01'"],
"description": ["Orders must reference a customer", "Orders must reference an active customer"]
},
"transaction": {
"id": 9876,
"issued_at": "2026-06-02T13:40:11Z",
"user": { "id": 42, "name": "Alice Lee" }
},
"operation": "update"
},
{
"changeset": {
"fields": [null, ["customer_id"]],
"status": [null, "Active"]
},
"transaction": {
"id": 8120,
"issued_at": "2026-01-15T09:02:47Z",
"user": { "id": 42, "name": "Alice Lee" }
},
"operation": "insert"
}
],
"total": 2,
"page": 1,
"size": 50,
"pages": 1
}
Each field inside items[].changeset is a [before, after] pair. The transaction object carries the change id, the issued_at timestamp, and the user who saved it. The operation field reports whether the entry is an insert, update, or delete. To load successive windows, increment page until it reaches pages.
Version at a Point in Time¶
Returns the full check definition as it stood at a given instant. Pass the start_time of a scan (from GET /api/operations/{id} or GET /api/container-scans/{id}) to retrieve the exact definition that scan evaluated, even if the check has been edited since.
Endpoint: GET /api/quality-checks/{id}/version?version_at={UTC timestamp}
Query parameters:
| Parameter | Type | Description |
|---|---|---|
version_at |
string (ISO 8601) |
The instant to resolve, interpreted as UTC (for example 2026-06-01T02:00:00Z). When omitted, the current definition is returned. A value earlier than the check's creation returns the first saved version. |
Example request
Example response (abbreviated)
{
"id": 778,
"rule_type": "notNull",
"description": "Orders must reference a customer",
"filter": "order_date >= '2026-01-01'",
"coverage": 1.0,
"status": "Active",
"fields": [{ "id": 3101, "name": "customer_id" }],
"container": { "id": 235, "name": "orders" },
"datastore": { "id": 101, "name": "Sales" },
"created": "2026-01-15T09:02:47Z",
"last_asserted": "2026-06-02T02:00:04Z"
}
The response has the same shape as GET /api/quality-checks/{id}, so any client that already reads a check can read a historical version. Deleted checks can be resolved as well.
The returned importance_level is the importance the check had at version_at, so a version requested before an importance change shows the earlier level.
Execution state is not versioned
Only the check's definition is restored to the requested instant. Execution state such as last_asserted, first_asserted, and active_anomaly_count, along with rule_type (which never changes), always reflects the check as it is now. In the example above, last_asserted is later than version_at because a scan ran after the requested instant. Use latest_container_scan_start_time from Insights or START_TIME in _scan_operations for the timing of a specific evaluation.
Insights¶
Returns the daily assertion history behind the Insights section of a check: for each day in the window, the result of the latest evaluation, the scan that produced it, and the asserted and anomalous record counts. This is the API equivalent of the pass/fail timeline in the UI and distinguishes checks that could not be evaluated from checks that passed.
Endpoint: GET /api/quality-checks/{id}/insights
Query parameters:
| Parameter | Type | Description |
|---|---|---|
report_date |
date |
Last day of the window, YYYY-MM-DD. Send it on every request. When omitted, the window can end on an earlier day instead of today. |
timeframe |
"week" \| "month" \| "quarter" \| "year" (default week) |
Length of the window ending on report_date. |
offset |
int |
Your timezone offset from UTC in minutes, so that days are bucketed in local time. Omit to bucket in UTC. |
Example request
Example response (abbreviated)
{
"generated_at": "2026-06-07T18:21:03Z",
"start_date": "2026-06-01",
"end_date": "2026-06-07",
"timeframe": "week",
"daily_assertion_measured": [
{
"date": "2026-06-01",
"latest_scan_id": 12345,
"latest_container_scan_id": 456,
"latest_container_scan_start_time": "2026-06-01T02:00:00Z",
"latest_partition_scan_id": 9001,
"latest_assertion_result": "passed",
"latest_asserted_records": 120000,
"latest_anomalous_records": 0,
"total_scans": 1,
"total_asserted_records": 120000,
"total_anomalous_records": 0
},
{
"date": "2026-06-02",
"latest_scan_id": 12360,
"latest_container_scan_id": 471,
"latest_container_scan_start_time": "2026-06-02T02:00:00Z",
"latest_partition_scan_id": 9017,
"latest_assertion_result": "failed",
"latest_asserted_records": 121500,
"latest_anomalous_records": 37,
"total_scans": 2,
"total_asserted_records": 243000,
"total_anomalous_records": 37
}
]
}
Daily metric fields:
| Field | Type | Description |
|---|---|---|
date |
date |
The day the metrics belong to. |
latest_scan_id |
int \| null |
The scan operation that last evaluated the check that day. Use it with GET /api/operations/{id}. |
latest_container_scan_id |
int \| null |
The container scan of that evaluation. |
latest_container_scan_start_time |
string \| null |
When that container scan started, in UTC. |
latest_partition_scan_id |
int \| null |
The partition scan of that evaluation. |
latest_assertion_result |
"passed" \| "failed" \| "unasserted" \| null |
Result of the latest evaluation. unasserted means the scan could not evaluate the check (for example, a filter with invalid syntax or a missing column). null means no scan evaluated the check that day. |
latest_asserted_records |
int \| null |
Records the latest evaluation asserted. |
latest_anomalous_records |
int \| null |
Records the latest evaluation flagged as anomalous. |
total_scans |
int \| null |
Number of evaluations that day. |
total_asserted_records |
int \| null |
Records asserted across all evaluations that day. |
total_anomalous_records |
int \| null |
Records flagged across all evaluations that day. |
Timestamp fields¶
| Field | Notes |
|---|---|
last_asserted |
Returned on every check payload. It is the UTC start time of the partition scan that last evaluated the check with a passed or failed result; unasserted evaluations do not move it. null for checks that have never been evaluated. Not versioned: the /version endpoint returns its current value. |
created |
When the check was created, in UTC. |
last_updated |
When the check was last updated, in UTC: the later of its last edit and its last importance change. A check whose importance changed but that nobody edited still gets a date. null when the check has neither. |
Ownership¶
Ownership is exposed on the standard quality check endpoints, with no dedicated route, through an owner_id field on create and update and an owner object on reads. For how ownership behaves, see Check Ownership.
The owner_id field¶
Every quality check carries a single owner. The API exposes it as owner_id on writes and as a nested owner object on reads.
| Field | Type | Description |
|---|---|---|
owner_id |
int |
The user ID of the check owner. On POST, defaults to the caller (or to the Qualytics user on AI Managed (inferred) checks). On PUT/PATCH, omit (or send null) to leave the owner unchanged. The target user must have at least the Drafter team permission on the check's datastore. |
Example owner field on a read response (abbreviated)
Create¶
Create a check with an explicit owner¶
Endpoint: POST /api/quality-checks
Permission: Member role + Author team permission on the container's datastore, or Drafter to create the check as a Draft. The target owner_id must have Drafter on the same datastore.
Include owner_id in the create payload to set an explicit owner. Omit the field to let the platform default to the caller.
Example request
Update¶
Transfer ownership on a single check¶
Endpoint: PUT /api/quality-checks/{id}
Permission: Member role + Author team permission on the check's datastore, or Drafter when the check is a Draft and the request does not change its status. The target owner_id must have Drafter on the same datastore.
Send owner_id set to the new owner's user ID. The previous owner is recorded in the check's history.
A PUT replaces the check's assertion, so send the current description and properties from GET /api/quality-checks/{id}, an array of each returned field's name as fields, and the new owner_id. For an owner-only change, the bulk update is simpler because it needs only id and owner_id.
Example request
curl -X PUT "https://your-instance.qualytics.io/api/quality-checks/101" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"description": "Ensure C_NAME is unique in the CUSTOMER table",
"fields": ["C_NAME"],
"properties": null,
"owner_id": 42
}'
This Unique check has no properties, so properties is null, as the GET returns it. For a check that has properties, send the properties object exactly as the GET returns it. Leaving properties out clears them.
AI Managed conversion still applies
For AI Managed (inferred) checks owned by Qualytics, changing the value of any assertion property (properties, coverage, filter, or fields) in the same payload also transfers ownership to the editor and flips inferred from true to false. To transfer ownership of an AI Managed check and keep inferred as true, use the bulk update with only id and owner_id.
Transfer ownership across many checks¶
Endpoint: PATCH /api/quality-checks
Permission: Member role + Author team permission on the datastore of every check in the request (Drafter is enough for checks that are Drafts and stay Drafts). The target owner_id must have Drafter on each of those datastores.
Send a list of { id, owner_id } entries; the same owner_id can be reused across entries to apply one owner to many checks. The same endpoint also takes the other bulk fields, such as tags and additional_metadata, with tags_mode and additional_metadata_mode set to replace (the default) or add; owner_id always replaces the current owner.
Bulk is all-or-nothing
If any entry references a check the caller cannot update (404 not found, missing the required team permission on the datastore, or the target owner_id lacks Drafter), the entire request is rejected and no checks are updated. Retry with the offending entry removed.
Apply the same owner to many checks
Filter¶
Filter checks by a specific owner¶
Endpoint: GET /api/quality-checks
Permission: Member role. Results are scoped to datastores the caller can see.
Use the owner_id query parameter to narrow the list to checks owned by a specific user. Combine with the other list filters (status, tags, rule type, and so on) to scope further.
List checks owned by user 42
List checks owned by the current user¶
Endpoint: GET /api/quality-checks
Permission: Member role.
Pass owner_id with a user's ID to list the checks that user owns. The Owned subtab sends your own user ID, which GET /api/users/me returns as id, together with the datastore and the status of the tab you are on.
Equivalent of the Owned subtab
Error responses¶
| Status | Description |
|---|---|
400 Bad Request |
A business-logic conflict prevents the operation (for example, attempting to change properties while the check status is invalid or discarded). |
401 Unauthorized |
Missing or invalid API token. |
403 Forbidden |
The caller lacks the required team permission on the check's datastore (Author, or Drafter for a Draft check), or the target owner_id lacks Drafter on the same datastore. |
404 Not Found |
The check ID or owner_id user does not exist. |
422 Unprocessable Entity |
The payload fails schema validation (for example, a required field such as description is missing on PUT, owner_id is not an integer, or properties contains an unrecognized key for the rule type). |
AI Managed¶
AI Managed checks use the same endpoints as every other check. For how they behave, see AI Managed Checks.
Telling AI Managed and Authored Checks Apart¶
Every check returned by the API includes an inferred boolean. When true, Qualytics AI manages the check; when false, the check is Authored and Profile runs leave it alone.
Coverage encoding
The API uses fractional values for coverage (0.0–1.0); the UI displays the same value as a percentage (0%–100%).
Example: filter AI Managed checks
Request:
curl -X GET "https://your-instance.qualytics.io/api/quality-checks?inferred=true" \
-H "Authorization: Bearer YOUR_API_TOKEN"
Response (abbreviated):
Editing an AI Managed Check¶
To edit an AI Managed check, send a PUT /api/quality-checks/{id} request with the check's whole definition. description is always required. fields is required unless the request keeps the check in Draft, and properties is required for rule types that have required properties (such as Between or Expected Values), sent in full because it replaces the stored one. Resend their current values along with the change. Whether the check stays AI Managed depends on which values the request changes, not on which fields it includes.
Permission: Drafter or above while the check stays in Draft. Author or above on an Active check, or for any status change other than restoring an archived check to Draft.
Edits that convert the check to Authored¶
Changing the value of any of the following fields flips inferred from true to false on save. Resending a field with its current value does not convert the check.
| Field | What it controls |
|---|---|
properties |
Rule-specific settings (thresholds, accepted values, regex pattern, and so on). |
coverage |
The percentage of records that must satisfy the rule. |
filter |
The SQL WHERE clause that scopes the rows the check evaluates. |
fields |
The list of fields the check applies to. |
Convert by lowering coverage
curl -X PUT "https://your-instance.qualytics.io/api/quality-checks/101" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"description": "price stays between 0 and 10000",
"fields": ["price"],
"properties": { "min": 0, "inclusive_min": true, "max": 10000, "inclusive_max": true },
"coverage": 0.995
}'
Response (abbreviated): inferred is now false and the response reflects the new coverage. Subsequent Profile runs will not modify or delete this check.
Edits that keep the check AI Managed¶
Changing only the following fields saves the change without converting the check, as long as the other values are resent unchanged:
| Field | What it controls |
|---|---|
description |
Free-text description shown in the UI. |
tags |
Tags assigned to the check. |
additional_metadata |
Key-value metadata pairs. |
anomaly_message_field |
The source-record field used as the anomaly message. |
status |
Active or Draft state. |
Add a tag without converting the check
curl -X PUT "https://your-instance.qualytics.io/api/quality-checks/101" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"description": "price stays between 0 and 10000",
"fields": ["price"],
"properties": { "min": 0, "inclusive_min": true, "max": 10000, "inclusive_max": true },
"tags": ["critical", "ops-reviewed"]
}'
Response (abbreviated): the check still has inferred: true. The next Profile run can continue to refresh it.
Conversion is silent
The conversion happens with no extra confirmation step. If you do not want to convert the check, resend properties and fields with their current values, leave coverage and filter out (or resend them unchanged), and change only organizational fields.
Deleting an AI Managed Check¶
Send DELETE /api/quality-checks/{id} to remove the check. Deleting an AI Managed check does not stop Qualytics from re-creating an equivalent check during the next Profile run if the data still suggests the rule.
Permission: Author or above.
Error Responses¶
| Status Code | Description |
|---|---|
400 Bad Request |
The payload is malformed (for example, missing a required field or sending an unexpected shape). |
401 Unauthorized |
Missing or invalid API token. |
403 Forbidden |
The user does not have the required team permission on the datastore. |
404 Not Found |
A check with the specified ID does not exist. |
422 Unprocessable Entity |
The payload is well-formed but a value fails schema validation (for example, a property that is not supported by the check's rule type). |