- Overview
- API Resources
Asynchronous table-level data-quality checks (freshness, missing-data, foreign-key) and constraint toggles, configured per table through the objectValidations field.
Object-level validations are asynchronous, table-level data-quality checks that evaluate a whole table rather than individual rows. Unlike the column-level validations applied inline during ingestion, they run after rollout, on demand, independently of any single ingest request. See Run validations on demand.
You configure them per table in an objectValidations array, set when you save a schema or add a custom object and updated later with Patch a solution's schema.
Entry fields
Each entry in the objectValidations array is an object:
| Field | Required | Description |
|---|---|---|
type | Yes | One of freshness, missing_data_validation, foreign_key, async_foreign_key, unique_key. |
enabled | Yes | Whether the rule or check is active. |
severity | Depends on type | error or warn. Required for freshness, missing_data_validation, and async_foreign_key; optional for foreign_key (defaults to warn); not allowed on unique_key. |
params | Depends on type | Rule parameters. Required for freshness and missing_data_validation; optional for foreign_key and async_foreign_key (only constraintName, see Routing constraints); not allowed on unique_key. Keys depend on type (see below). |
alerts | Optional | Names of the alert channels to notify when the rule fails. Available on freshness, missing_data_validation, and async_foreign_key. See Data quality alerts. |
Data-quality rules
-
freshness— flags a table whose most-recent record is older than a threshold.params:Param Description columnTimestamp or date column to measure recency on. thresholdNumeric age limit. unitOne of minutes,hours,days,months,years. -
missing_data_validation— flags a time window that received fewer rows than expected.params:Param Description columnTimestamp or date column that defines the window. intervalNumeric length of the window. unitOne of minutes,hours,days,months,years.minRowCountMinimum number of rows expected in the window.
Foreign-key checks
The foreign-key check verifies that every value in a foreign-key column has a matching row in the referenced table. Two entries configure it, and you can use both on the same table:
foreign_key— runs the check inline as rows are ingested, scoped to the rows in each load. How a foreign-key violation is handled during ingestion — flagged on the row or routed to<table_name>_failed_rows— is described under Validation behavior.severityis optional and defaults towarn.async_foreign_key— an object-level check that re-evaluates the whole table after rollout, on demand, independently of any ingest. Like every object-level validation it does not move any row — it flags each orphan row in place;severityis required.
Because async_foreign_key re-checks every row on each run, a row flagged only because its parent had not yet been loaded is cleared automatically once the parent arrives — so a table no longer has to be ingested parent-before-child.
Both entries flag a violating row through the same two audit columns on the table:
| Column | Description |
|---|---|
peakAuditErrors | JSON array of the foreign-key violations on the row, each { "errorCode", "errorDetails", "severity", "constraintName", "flaggedAt" }. |
peakAuditLastValidationTime | Timestamp of the run that last flagged the row. |
flaggedAt records when that violation was first seen. A later run that finds the same violation still unresolved keeps the original timestamp, so flaggedAt tells you how long a reference has been missing; peakAuditLastValidationTime is the row's most recent check.
Your downstream pipelines read peakAuditErrors to skip or specially handle flagged rows. When a later run finds that the parent row now exists, both columns are cleared for that row.
See Audit columns added at rollout for the columns and their per-warehouse casing, and the DI_{E|W}_23F01 error code reported for each violation.
Routing constraints with params.constraintName
Both foreign_key and async_foreign_key accept an optional params.constraintName — an array of foreign-key constraint names. When present, the entry applies only to the listed constraints; when omitted, it applies to all foreign keys on the table.
This lets you route some constraints to the per-load foreign_key check and others to the whole-table async_foreign_key check on the same table — for example, enforce a stable reference (a currency or region table) during ingestion, while re-checking a constraint whose parent rows arrive in a separate feed asynchronously.
An async_foreign_key run rewrites peakAuditErrors on every row from the constraints it evaluated. On a table with more than one foreign key, scoping the entry to a subset therefore clears the flags its other constraints had set — so constraintName is best omitted unless that scoping is specifically needed.
Unique-key and primary-key checks
A unique_key entry enables or disables the asynchronous unique-key collision check for the table — it carries only type and enabled (no params, no severity). For example, { "type": "unique_key", "enabled": false } turns the check off. The primary-key check is always on and cannot be disabled.
The foreign_key / async_foreign_key / unique_key entries are distinct from the structural foreignKeys / uniqueKeys per-table fields. Those define the constraints (which columns form a key, which table is referenced); the objectValidations entries only control whether — and how — the corresponding check runs.
Alerts
Add an alerts array to a freshness, missing_data_validation, or async_foreign_key entry to notify a Slack or email channel when it fails. The names must match channels registered for your tenant, and the reserved name __ALL__ expands to all of them. Alerts are sent only when a check finishes in a failed or error state.
Channels are registered once per tenant through /api/v2/alert-configs, which is covered in Data quality alerts. A name that matches no registered channel is rejected with 400 Bad Request when you save the validation.
Defaults
- The
objectValidationsarray is optional. Omit it and the table has no data-quality rules; its foreign-key, unique-key, and primary-key checks still run with their default behavior. - A check you don't list runs on by default — the
foreign_key,async_foreign_key, andunique_keychecks are active for a table that declares the corresponding keys, unless you add an entry with"enabled": false. The primary-key check is always on and cannot be disabled. foreign_keyseverity defaults towarnwhen omitted;async_foreign_keyrequires an explicitseverity, andfreshness/missing_data_validationrequire bothseverityandparams.- Within an entry there are no other implicit defaults:
typeandenabledare always required. Omitting a required field returns400 Bad Request.
Example
"objectValidations": [
{ "type": "freshness",
"params": { "column": "order_date", "threshold": 24, "unit": "hours" },
"enabled": true, "severity": "error" },
{ "type": "missing_data_validation",
"params": { "column": "order_date", "interval": 1, "unit": "days", "minRowCount": 100 },
"enabled": true, "severity": "warn" },
{ "type": "foreign_key", "enabled": true, "severity": "warn",
"params": { "constraintName": ["fk_orders_customer"] } },
{ "type": "async_foreign_key", "enabled": true, "severity": "warn",
"alerts": ["data-oncall"],
"params": { "constraintName": ["fk_orders_region"] } },
{ "type": "unique_key", "enabled": false }
]
"objectValidations": [
{ "type": "freshness",
"params": { "column": "order_date", "threshold": 24, "unit": "hours" },
"enabled": true, "severity": "error" },
{ "type": "missing_data_validation",
"params": { "column": "order_date", "interval": 1, "unit": "days", "minRowCount": 100 },
"enabled": true, "severity": "warn" },
{ "type": "foreign_key", "enabled": true, "severity": "warn",
"params": { "constraintName": ["fk_orders_customer"] } },
{ "type": "async_foreign_key", "enabled": true, "severity": "warn",
"alerts": ["data-oncall"],
"params": { "constraintName": ["fk_orders_region"] } },
{ "type": "unique_key", "enabled": false }
]
Run validations on demand
Object-level validations do not run by themselves. Starting a run for a table evaluates all its enabled async rules — freshness, missing_data_validation, and async_foreign_key — against the data already in it. The call returns immediately with a run_id.
This endpoint is served on the same host as the rest of this guide, under the /validation-api base path:
POST https://ingestion.peak.ai/validation-api/api/v1/validations/run
POST https://ingestion.peak.ai/validation-api/api/v1/validations/run
Spoke tenants have their own host here too, as described in API host and reference: https://ingestion.<cluster-identifier>.peak.ai/validation-api/api/v1/validations/run. Both validation endpoints are listed in the Swagger reference under the Validation API section: https://ingestion.peak.ai/api-docs/.
Payload
| Field | Required | Description |
|---|---|---|
solution_name | Yes | Solution that owns the table |
table | Yes | Table to validate, by bare name (orders) or schema-qualified name (public.orders) |
rule_types | No | Array of rule types to run, so a subset can be re-checked — for example ["async_foreign_key"]. When omitted, every enabled async rule on the table runs. |
overrides | No | Per-run numeric parameter overrides, keyed by rule type — freshness: threshold, unit; missing_data_validation: interval, minRowCount, unit. Structural fields such as column cannot be overridden. |
Example request
A run scoped to the foreign keys alone — the usual call once a parent table's feed has landed:
curl -X POST \
'https://ingestion.peak.ai/validation-api/api/v1/validations/run' \
-H 'Authorization: <your-api-key>' \
-H 'Content-Type: application/json' \
-d '{
"solution_name": "QP_OOTB",
"table": "QP_CUSTOMER_ORDERS_OOTB",
"rule_types": ["async_foreign_key"]
}'
curl -X POST \
'https://ingestion.peak.ai/validation-api/api/v1/validations/run' \
-H 'Authorization: <your-api-key>' \
-H 'Content-Type: application/json' \
-d '{
"solution_name": "QP_OOTB",
"table": "QP_CUSTOMER_ORDERS_OOTB",
"rule_types": ["async_foreign_key"]
}'
Response (202 Accepted)
{
"run_id": "09a911f9-696f-44c8-9cbb-708ae9adae18",
"status": "running"
}
{
"run_id": "09a911f9-696f-44c8-9cbb-708ae9adae18",
"status": "running"
}
Status codes
202 Accepted— the run started; the response carries therun_id400 Bad Request— unknown solution or table, no enabled validations on the table (or none matchingrule_types), an unknown rule type, an invalid override value, or the tenant's warehouse credentials could not be resolved
Check a validation run
Returns the current state of a run, which stays running until the engine reaches a terminal state.
GET https://ingestion.peak.ai/validation-api/api/v1/validations/run/{run_id}
GET https://ingestion.peak.ai/validation-api/api/v1/validations/run/{run_id}
Path parameters
run_id(required) — the identifier returned when the run started
Response (200 OK)
While the engine is still working, the body carries only the status:
{ "run_id": "09a911f9-696f-44c8-9cbb-708ae9adae18", "status": "running" }
{ "run_id": "09a911f9-696f-44c8-9cbb-708ae9adae18", "status": "running" }
On success the body is the full run report — one entry per rule evaluated, and no status field:
{
"run_id": "09a911f9-696f-44c8-9cbb-708ae9adae18",
"results": [
{
"rule_type": "async_foreign_key",
"scope": "multi_table",
"target_tables": ["STAGE.QP_CUSTOMER_ORDERS_OOTB"],
"columns": [],
"status": "failed",
"severity": "warn",
"error_codes": ["DI_W_23F01"],
"message": "Foreign-key violations: 2 row(s) reference a non-existent parent (kept in the destination and flagged)",
"details": {
"constraints": [
{
"constraintName": "fk_orders_customer",
"childColumns": ["customer_id"],
"parentTable": "STAGE.QP_CUSTOMER_OOTB",
"parentColumns": ["customer_id"],
"orphanCount": 2
}
],
"totalOrphanRows": 2
},
"params": {},
"alert_names": ["data-oncall"],
"alerts_sent": ["data-oncall"]
}
]
}
{
"run_id": "09a911f9-696f-44c8-9cbb-708ae9adae18",
"results": [
{
"rule_type": "async_foreign_key",
"scope": "multi_table",
"target_tables": ["STAGE.QP_CUSTOMER_ORDERS_OOTB"],
"columns": [],
"status": "failed",
"severity": "warn",
"error_codes": ["DI_W_23F01"],
"message": "Foreign-key violations: 2 row(s) reference a non-existent parent (kept in the destination and flagged)",
"details": {
"constraints": [
{
"constraintName": "fk_orders_customer",
"childColumns": ["customer_id"],
"parentTable": "STAGE.QP_CUSTOMER_OOTB",
"parentColumns": ["customer_id"],
"orphanCount": 2
}
],
"totalOrphanRows": 2
},
"params": {},
"alert_names": ["data-oncall"],
"alerts_sent": ["data-oncall"]
}
]
}
A status of failed on a result means the rule found a problem. If the run itself could not complete, the body reports that instead:
{ "status": "failed", "error": "warehouse connection timed out" }
{ "status": "failed", "error": "warehouse connection timed out" }
Status codes
200 OK— the run exists; the presence and value ofstatusdistinguish the three bodies404 Not Found— no run with that identifier, or its result has expired
Results
Object-level validations run asynchronously. Each run's own report is available from Check a validation run, and the outcomes across runs surface in the Data Quality Dashboard.
Failures are reported under the OBJECT_VALIDATION error codes — DI_{E|W}_24F01 (freshness), DI_{E|W}_24M01 (missing data), and DI_{E|W}_23F01 (foreign key), where E is an error and W a non-fatal warning, set by the entry's severity.
Foreign-key violations are flagged on the row through peakAuditErrors / peakAuditLastValidationTime (see Foreign-key checks); a later async_foreign_key run clears the flag once the parent row exists.
When a rule names alert channels, the run report also lists the channels notified for that result in alerts_sent. A channel that could not be reached is left out, and the run still completes.
- Entry fields
- Data-quality rules
- Foreign-key checks
- Routing constraints with
params.constraintName - Unique-key and primary-key checks
- Alerts
- Defaults
- Example
- Run validations on demand
- Payload
- Example request
- Response (202 Accepted)
- Status codes
- Check a validation run
- Path parameters
- Response (200 OK)
- Status codes
- Results