REST API
API: Data Quality
Scans, column profiles, tests, issues, and quality scores. All endpoints require authentication unless noted. 33 endpoints, 1 of them app only: an API key gets 403 there.
GET /api/data-quality/check-suggestions
Section titled “GET /api/data-quality/check-suggestions”List the checks suggested from the workspace’s data models (rule-based; uses no AI credits)
Worked out from the models when read: not null and unique on a table’s declared key (duplicate detection on a key of several columns), a foreign key check for each one-to-many or one-to-one relationship, no future dates on a metric’s time dimension when it is a date or a date and time, and a row count on each table a card on a live dashboard reads. Ranked by how many cards, metrics and measures read the field, then certified first. A check an existing test already makes is never suggested; a dismissed one comes back 90 days after it was dismissed. Only models on sources data-quality tests run on. A check on a table two models read is listed once, under either model. One page, with the total.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
modelId |
string | no | |
limit |
integer | no | |
offset |
integer | no |
Responses: 200 · 400 · 401
POST /api/data-quality/check-suggestions/accept
Section titled “POST /api/data-quality/check-suggestions/accept”Accept suggested checks: each becomes a data quality test
Creates one test per suggestion, as the model names its table and field, all of them or none. An id the list does not hold now (someone acted on it, or a test makes it now) refuses the request with SUGGESTIONS_CHANGED (409) and details.stale, and nothing is created.
Request body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
ids |
array of string | yes | Up to 200 suggestion ids at once; more is refused. |
severity |
"critical" | "high" | "medium" | "low" | "info" |
no | The tests’ severity (medium when left out). Critical holds publishing and needs the Business plan. |
Responses: 200 · 400 · 403 · 409
POST /api/data-quality/check-suggestions/dismiss
Section titled “POST /api/data-quality/check-suggestions/dismiss”Dismiss suggested checks
Each is not suggested again until 90 days later. An id the list does not hold now refuses the request with SUGGESTIONS_CHANGED (409) and details.stale, and nothing is dismissed.
Request body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
ids |
array of string | yes | Up to 200 suggestion ids at once; more is refused. |
Responses: 200 · 400 · 403 · 409
GET /api/data-quality/freshness
Section titled “GET /api/data-quality/freshness”Get table freshness information
Responses: 200
GET /api/data-quality/issues
Section titled “GET /api/data-quality/issues”List quality issues (one page, with the filtered total)
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
status |
"open" | "ignored" | "fixed" | "in_progress" |
no | |
severity |
"critical" | "high" | "medium" | "low" | "info" |
no | |
issueType |
string | no | |
tableName |
string | no | |
limit |
integer | no | |
offset |
integer | no |
Responses: 200 · 400 · 401
PUT /api/data-quality/issues/{id}/status
Section titled “PUT /api/data-quality/issues/{id}/status”Update issue status
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id |
string | yes |
Request body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
status |
"open" | "ignored" | "fixed" | "in_progress" |
no | |
resolutionNote |
string | no |
Responses: 200
GET /api/data-quality/issues/summary
Section titled “GET /api/data-quality/issues/summary”Get issue summary by status/severity/type/table
Responses: 200
GET /api/data-quality/profiles/{tableName}
Section titled “GET /api/data-quality/profiles/{tableName}”Get latest column profiles for a table
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
tableName |
string | yes |
Responses: 200
GET /api/data-quality/report-status
Section titled “GET /api/data-quality/report-status”Get quality status for tables used in a report
Returns aggregated quality score, freshness status, and issues for the specified tables. Used by Report Builder to show data quality warnings.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
tableNames |
string | no | Comma-separated list of table names |
modelId |
string | no | |
reportId |
string | no |
Responses: 200
GET /api/data-quality/row-count-changes
Section titled “GET /api/data-quality/row-count-changes”List the row-count check’s changes not yet acknowledged or dismissed
Newest first: each table whose exact row count changed by 25% or more between two row-count checks, with both counts and when each was taken. The title and description name the table as the model calls it now. A change on a table or model removed since is not listed. Rule-based: it uses no AI credits.
Responses: 200 · 400 · 401
POST /api/data-quality/row-count-changes/{id}/acknowledge
Section titled “POST /api/data-quality/row-count-changes/{id}/acknowledge”Acknowledge a row-count change
Marks the change as seen and takes it off the list. Another workspace’s change answers 404, as an unknown id does.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id |
string | yes |
Responses: 200 · 400 · 401 · 404
POST /api/data-quality/row-count-changes/{id}/dismiss
Section titled “POST /api/data-quality/row-count-changes/{id}/dismiss”Dismiss a row-count change
Takes the change off the list as not worth following up. Another workspace’s change answers 404, as an unknown id does.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id |
string | yes |
Responses: 200 · 400 · 401 · 404
POST /api/data-quality/scan
Section titled “POST /api/data-quality/scan”Run the row-count check now (rule-based; uses no AI credits)
Compares the latest exact row count recorded for each table of the workspace’s data models with the count the previous check saved; the check does not query the source. The latest count is a Warehouse dataset’s at its last refresh (for the dataset’s own table), else the one the last read of the source’s tables took (a Sync, or the read after a scheduled refresh or a file replacement). Only exact counts are compared: those of uploaded files and connector data, which DataSquares counts when it reads them, of databases that keep an exact count, and of ClickHouse’s plain MergeTree tables (not those whose merges change their count: the other engines, a table TTL, a lightweight delete). A database’s estimate from its statistics is not compared, and is counted in tablesEstimated. Two counts are compared only when they were taken the same way, and only when the count is new since the previous check. A table seen for the first time, or counted another way than at the previous check, has its count saved for the next one. A change of 25% or more, from a table that had at least 100 rows, is recorded once and listed by GET /api/data-quality/row-count-changes. The same check runs once a day for every workspace.
Responses: 200
GET /api/data-quality/scans
Section titled “GET /api/data-quality/scans”Get scan history
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
tableName |
string | no | |
limit |
number | no |
Responses: 200
POST /api/data-quality/scans
Section titled “POST /api/data-quality/scans”Start a new data quality scan for a table
Request body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
modelId |
string | no | |
tableId |
string | no | |
sourceId |
string | no | |
schemaName |
string | no | |
tableName |
string | yes | |
config |
object | no | |
aiFindings |
boolean | no | true adds AI findings: the AI ranks the rule-based findings, sharpens their wording and may add up to five, when the AI service is configured; it uses AI credits. Without it the scan runs the rules only and spends no AI credits. API keys can’t ask for it. |
Responses: 200
GET /api/data-quality/scans/{scanId}
Section titled “GET /api/data-quality/scans/{scanId}”Get scan by ID
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
scanId |
string | yes |
Responses: 200
GET /api/data-quality/scans/{scanId}/profiles
Section titled “GET /api/data-quality/scans/{scanId}/profiles”Get column profiles for a scan
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
scanId |
string | yes |
Responses: 200
GET /api/data-quality/score
Section titled “GET /api/data-quality/score”Get overall workspace quality score
Responses: 200
GET /api/data-quality/score/tables
Section titled “GET /api/data-quality/score/tables”Get quality scores for each table
Responses: 200
GET /api/data-quality/suggestions
Section titled “GET /api/data-quality/suggestions”List quality suggestions
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
status |
"pending" | "applied" | "dismissed" | "expired" |
no | |
limit |
number | no |
Responses: 200
POST /api/data-quality/suggestions/{id}/apply
Section titled “POST /api/data-quality/suggestions/{id}/apply”Apply a suggestion
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id |
string | yes |
Responses: 200
POST /api/data-quality/suggestions/{id}/dismiss
Section titled “POST /api/data-quality/suggestions/{id}/dismiss”Dismiss a suggestion
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id |
string | yes |
Responses: 200
GET /api/data-quality/summary
Section titled “GET /api/data-quality/summary”Count the row-count changes not yet acknowledged or dismissed, by size
Counts exactly the changes GET /api/data-quality/row-count-changes lists (on live tables of live models), by how much the row count changed: high 75% or more, medium 50% or more, low below (the check records changes of 25% or more). Rule-based: it uses no AI credits.
Responses: 200
GET /api/data-quality/tests
Section titled “GET /api/data-quality/tests”List data quality tests for workspace
Responses: 200
POST /api/data-quality/tests
Section titled “POST /api/data-quality/tests”Create a data quality test
Request body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | |
description |
string | no | |
testType |
"not_null" | "unique" | "value_range" | "row_count" | "email" | "phone" | "regex" | "allowed_values" | "date_range" | "no_future_date" | "no_negative" | "foreign_key" | "duplicate_detection" | "custom_sql" |
yes | |
targetType |
"source_table" | "model_field" |
yes | |
sourceId |
string | no | |
modelId |
string | no | |
tableId |
string | no | |
fieldId |
string | no | |
schemaName |
string | no | |
tableName |
string | no | |
columnName |
string | no | |
config |
object | no | |
scheduleEnabled |
boolean | no | |
scheduleCron |
string | no | |
runOnSync |
boolean | no | |
severity |
"critical" | "high" | "medium" | "low" | "info" |
no |
Responses: 201 · 400 · 403
GET /api/data-quality/tests/{id}
Section titled “GET /api/data-quality/tests/{id}”Get a data quality test by ID
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id |
string | yes |
Responses: 200
PUT /api/data-quality/tests/{id}
Section titled “PUT /api/data-quality/tests/{id}”Update a data quality test
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id |
string | yes |
Request body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | no | |
description |
string | null | no | |
testType |
"not_null" | "unique" | "value_range" | "row_count" | "email" | "phone" | "regex" | "allowed_values" | "date_range" | "no_future_date" | "no_negative" | "foreign_key" | "duplicate_detection" | "custom_sql" |
no | |
targetType |
"source_table" | "model_field" |
no | |
sourceId |
string | null | no | |
modelId |
string | null | no | |
tableId |
string | null | no | |
fieldId |
string | null | no | |
schemaName |
string | null | no | |
tableName |
string | null | no | |
columnName |
string | null | no | |
config |
object | no | |
scheduleEnabled |
boolean | no | |
scheduleCron |
string | null | no | |
runOnSync |
boolean | no | |
severity |
"critical" | "high" | "medium" | "low" | "info" |
no | |
isActive |
boolean | no |
Responses: 200 · 400 · 403
DELETE /api/data-quality/tests/{id}
Section titled “DELETE /api/data-quality/tests/{id}”Delete a data quality test
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id |
string | yes |
Responses: 204
POST /api/data-quality/tests/{id}/run
Section titled “POST /api/data-quality/tests/{id}/run”Run a data quality test manually
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id |
string | yes |
Responses: 200
GET /api/data-quality/tests/{id}/runs
Section titled “GET /api/data-quality/tests/{id}/runs”Get run history for a data quality test
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id |
string | yes |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
limit |
number | no |
Responses: 200
POST /api/data-quality/tests/draft
Section titled “POST /api/data-quality/tests/draft”Draft one check from a sentence (Describe the check; uses AI credits)
App only. An API key gets 403: AI features are available in the DataSquares app; API keys can’t use them.
The AI is sent the sentence and the table’s field names, types and the latest profiling scan’s counts, and the fields of the tables the model joins it to: never a stored value, and a labelled field by its name and type only. It answers with one typed check (never custom SQL) that must pass the same checks as a saved test, with one repair round. Nothing is saved: the draft is what POST /tests takes. Uses AI credits, charged to the person who asks. Refused before any AI call: a model on a source data-quality checks don’t run on (409 DQ_SOURCE_NOT_SUPPORTED) and a table outside the model’s AI scope (409 OUTSIDE_AI_SCOPE). A sentence no single check fits, or a reply that can’t be repaired, answers 422.
Request body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
sentence |
string | yes | What the check must make sure of, in the person’s words. |
modelId |
string | yes | |
tableId |
string | yes | The model’s table the check is on. |
Responses: 200 · 403 · 404 · 409 · 422 · 429 · 503
POST /api/data-quality/tests/dry-run
Section titled “POST /api/data-quality/tests/dry-run”Run a check once without saving it (a dry run; rule-based, uses no AI credits)
Runs the check in the body once against its source, exactly as a saved test’s run would, and answers whether it passes, with its counts. Nothing is stored: no test, no run, no issue. A custom SQL check meets the safety check saving it would (403 with the reason); its sample rows come back only when its query provably reads no labelled column. A check whose source or table can’t be found, or that the source refuses, answers status: error with the reason, as a run does. Refused to anyone a row-level security policy in the workspace applies to.
Request body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | no | |
description |
string | no | |
testType |
"not_null" | "unique" | "value_range" | "row_count" | "email" | "phone" | "regex" | "allowed_values" | "date_range" | "no_future_date" | "no_negative" | "foreign_key" | "duplicate_detection" | "custom_sql" |
yes | |
targetType |
"source_table" | "model_field" |
yes | |
sourceId |
string | no | |
modelId |
string | no | |
tableId |
string | no | |
fieldId |
string | no | |
schemaName |
string | no | |
tableName |
string | no | |
columnName |
string | no | |
config |
object | no | |
scheduleEnabled |
boolean | no | |
scheduleCron |
string | no | |
runOnSync |
boolean | no | |
severity |
"critical" | "high" | "medium" | "low" | "info" |
no |
Responses: 200 · 400 · 403
POST /api/data-quality/tests/run-all
Section titled “POST /api/data-quality/tests/run-all”Run all active data quality tests
Responses: 200