# Object-level validations

> 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 a schedule or on demand — independently of any single ingest request.

You configure them per table in an `objectValidations` array, set when you [save a schema](schema-lifecycle.md#schema-definition-structure) or [add a custom object](schema-lifecycle.md#add-a-custom-object) and updated later with [Patch a solution's schema](schema-lifecycle.md#patch-a-solutions-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](#routing-constraints-with-paramsconstraintname)); 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-alerts.md). |

## Data-quality rules

- **`freshness`** — flags a table whose most-recent record is older than a threshold. `params`:

  | Param | Description |
  |-------|-------------|
  | `column` | Timestamp or date column to measure recency on. |
  | `threshold` | Numeric age limit. |
  | `unit` | One of `minutes`, `hours`, `days`, `months`, `years`. |

- **`missing_data_validation`** — flags a time window that received fewer rows than expected. `params`:

  | Param | Description |
  |-------|-------------|
  | `column` | Timestamp or date column that defines the window. |
  | `interval` | Numeric length of the window. |
  | `unit` | One of `minutes`, `hours`, `days`, `months`, `years`. |
  | `minRowCount` | Minimum 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](api-guide.md#validation-behavior). `severity` is optional and defaults to `warn`.
- **`async_foreign_key`** — an object-level check that re-evaluates the **whole table** after rollout, on a schedule or on demand, independently of any ingest. Like every object-level validation it does not move any row — it **flags** each orphan row in place; `severity` is 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" }`. |
| `peakAuditLastValidationTime` | Timestamp of the run that last flagged the row. |

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](schema-lifecycle.md#audit-columns-added-at-rollout) for the columns and their per-warehouse casing, and the `DI_{E|W}_23F01` [error code](api-guide.md#error-codes) 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.

## 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.

:::note
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](data-quality-alerts.md). A name that matches no registered channel is rejected with `400 Bad Request` when you save the validation.

## Defaults

- The `objectValidations` array 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`, and `unique_key` checks 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_key` severity defaults to `warn` when omitted; `async_foreign_key` requires an explicit `severity`, and `freshness` / `missing_data_validation` require both `severity` and `params`.
- Within an entry there are no other implicit defaults: `type` and `enabled` are always required. Omitting a required field returns `400 Bad Request`.

## Example

```json
"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 }
]
```

## Results

Object-level validations run asynchronously, and their outcomes surface in the [Data Quality Dashboard](data-quality-dashboard.md).

Failures are reported under the `OBJECT_VALIDATION` [error codes](api-guide.md#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](#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.
