Skip to content

Data contracts (ODCS)

A data contract is a team's written promise about a dataset: its columns, the quality rules it keeps and how fresh it stays. DataQ reads contracts in the Open Data Contract Standard (ODCS) v3 and turns them into ordinary suites and checks that run, alert and score like any other. This is the consumer side: DataQ checks data that has already landed against the contract. It does not enforce anything inside the producer's pipeline.

Import a contract

In the app, open Contracts in the sidebar and choose Import contract: pick the connection the data lives on and paste the document or choose the file. Over the API, send the ODCS document, as YAML text or JSON, with the connection the data lives on:

curl -X POST "$DATAQ/api/v1/contracts/import" \
  -H "Authorization: Bearer $DATAQ_TOKEN" -H "Content-Type: application/json" \
  -d "$(jq -n --arg yaml "$(cat orders.odcs.yaml)" --arg conn "$CONNECTION_ID" \
        '{connection_id: $conn, document_yaml: $yaml}')"

DataQ validates the document against the ODCS JSON Schema for its apiVersion (v3.0.0 to v3.2.0; a newer v3 minor is accepted with a warning; v2 and v4 are refused), stores it exactly as sent, and creates one suite per schema object, targeted at the object's physicalName (schema.table, catalog.schema.table on Unity Catalog, a path on object stores). The response lists the suites it created, any object whose name did not resolve to a table on that connection (its suite exists but cannot run until you set a target), and every clause it could not enforce.

Importing requires the Member role. Each version of a contract is stored once; importing the same id and version again is refused unless you send replace: true.

What becomes a check

In the contract In DataQ
required: true not-null check
unique: true, a single primaryKey uniqueness check
several primaryKey properties compound uniqueness
logicalTypeOptions minimum / maximum (exclusive too), length, pattern range, length and regex checks
enum / invalidValues with validValues or pattern value-set or regex check
nullValues, missingValues, duplicateValues not-null, not-in-set, uniqueness checks; a percent bound becomes the fail threshold
rowCount a volume monitor; a one-sided bound stays one-sided
the object's property list a schema-drift check whose baseline is the declared columns
type: sql rules a custom SQL check that fails when the query's result breaks the operator, or is empty
type: custom with engine: great-expectations that expectation, if DataQ allows it
slaProperties latency on a timestamp column a freshness monitor
classification of confidential, restricted, pii, sensitive or secret the column joins the suite's PII columns, so failing samples mask it
severity info / warning / error / critical warn / warn / fail / critical; no severity means fail
dimension DataQ's dimension (conformity reads as validity; coverage stays unclassified)

A schedule for an object's suite comes from a dataq.schedule cron in the object's customProperties, and dataq.maxAgeHours (with dataq.freshnessColumn) adds a freshness ceiling.

Not enforced

Everything else is kept on the contract as not enforced, with the reason, and never silently dropped. That includes frequency (a refresh cadence is not a staleness limit; state the limit as latency), retention and the other service-level properties, text rules, rules for another engine such as Soda, a rows bound on nullValues, multipleOf, and any check DataQ's own validators refuse for that datasource.

A new version

Import the new version with suite_ids set to the suites the old version created. DataQ re-points them and reconciles their checks: clauses that are new get a check, a check nobody has edited since the import is updated to the new clause, a check whose clause disappeared is switched off (not deleted, so its history stays), and checks people added to the suite themselves are left alone. Without suite_ids, the new version creates fresh suites.

Compliance

A contract's page in the app shows the same rollup, with a warning when a suite's latest run has not finished or some suites are hidden from you, and a Download ODCS button. Over the API, GET /api/v1/contracts/{id} carries a compliance block: for every clause a check enforces, the state that check reached in its suite's latest run — pass, warn, fail, critical, unverified (the check errored or was skipped), not_run (that run has no result for it), disabled, or missing (the suite has no check for the clause at all, because it was deleted or the suite was linked by hand) — and a drifted flag when someone has edited the check since the import, so it may no longer say what the clause says. Each suite reports its latest run and results_final, which is false while that run is still going: the states are partial until then. The clauses DataQ does not enforce are counted as not_enforced.

When a contract-derived check opens an incident, the incident's evidence names the contract and the clause it broke, and alerts lead with it.

To attach a suite you already have to a contract's schema object, send PATCH /api/v1/suites/{id} with {"contract": {"contract_id": "…", "contract_object": "orders"}}, or {"contract": null} to detach it. Linking adds no checks; importing the contract does.

Export

GET /api/v1/contracts/{id}/export returns the contract as an ODCS document (JSON, or YAML with ?format=yaml): the stored document plus dataq.* custom properties naming the check behind each rule, and every check people added to the suites as a type: custom, engine: dataq rule. Importing the export recreates every check.

Who sees a contract

You see a contract when you can view at least one of its suites; the response counts the linked suites you cannot see without naming them. Workspace admins also see contracts with no linked suite. Deleting a contract needs edit on all of its suites, and only unlinks them: no suite, check or result is deleted.

Contracts and comparison checks

A comparison check reconciles two datasets against each other. A contract describes one dataset against a written promise. They answer different questions and can be used together.