What is a data contract? A practical definition

What a data contract is, what one actually contains, how it differs from a schema or an SLA, and how it gets enforced — with a worked ODCS example.

· 8 min read

A data contract is an explicit, versioned agreement between the producer of a dataset and its consumers that specifies what the dataset guarantees: its schema, the meaning of each field, the data quality expectations it must meet, and the service levels — freshness, availability, support — under which it is delivered. It is written as a machine-readable document, stored in version control alongside code, reviewed through pull requests, and enforced automatically by running its quality expectations against the real data. A contract is a promise about an interface, not a description of a pipeline.

The word "contract" is doing real work in that definition. It implies two named parties, a stated obligation, a version that can be pointed at, and consequences when the obligation is not met. A schema file has none of those properties; a contract has all four.

Why data contracts emerged

Three failure modes drove teams to the idea, and they are worth naming because they explain the shape of the solution.

Schema drift with no notification. A producer team renames a column, widens a type or drops a field they believe is unused. The change passes their tests, because their tests cover their code. Downstream, a dashboard breaks, a model silently retrains on nulls, or a nightly job fails at 03:00. There was no interface, so there was nothing to break — only a table that changed.

Broken dashboards with no accountable owner. When quality degrades, the conversation starts with archaeology. Who produces this table? What was it supposed to contain? Was 4% nulls in customer_id always normal? Nobody wrote it down, so the answer takes a week and the fix is a patch downstream rather than upstream.

Producer and consumer coupling. Without a stated interface, consumers reverse-engineer the producer's implementation — reading intermediate tables, depending on incidental orderings, hard-coding assumptions about a status field's values. Every one of those becomes an unwritten obligation the producer does not know they have.

The software industry solved the analogous problem with API contracts: OpenAPI specifications, semantic versioning, deprecation windows. A data contract applies the same discipline at the data interface. The core insight is that the promise itself becomes an artefact, separate from the pipeline that fulfils it and separate from the tool that checks it.

What a data contract contains

A complete contract carries five things.

  1. Identity and ownership. What the dataset is, which team owns it, how to reach them, what the contract's own version is. Ownership is not decoration — it is the party on the other side of the agreement.
  2. Schema and semantics. The fields, their types (both logical and physical), whether they are required, and — crucially — what each one means. amount is not a description; "order total in EUR, excluding VAT, after discounts" is.
  3. Quality expectations. The rules the data must satisfy, each with a threshold and a severity: required columns are never null, keys are unique, categorical fields stay within an allowed set, numbers stay within a plausible range, foreign keys resolve.
  4. Service levels. Freshness (how recently the data must have been updated), availability, retention, and the support expectations that go with them.
  5. Change policy. How the contract is versioned, what counts as breaking, and how much notice consumers get before a breaking change lands.

Here is the shape in practice, written in the Open Data Contract Standard:

apiVersion: v3.0.0
kind: DataContract
info:
  title: orders
  version: 2.1.0
  owner: data-platform
schema:
  - name: orders
    physicalName: orders
    physicalType: table
    properties:
      - name: order_id
        logicalType: string
        physicalType: uuid
        required: true
        primaryKey: true
        description: Business key, stable across restatements.
        quality:
          - rule: nullCount
            dimension: completeness
            severity: error
            mustBe: "0"
          - rule: duplicateCount
            dimension: uniqueness
            severity: error
            mustBe: "0"
      - name: status
        logicalType: string
        description: Lifecycle state. New values require a minor version bump.
        quality:
          - rule: validValues
            dimension: conformity
            severity: error
            mustBe: "['pending', 'paid', 'shipped', 'refunded']"
      - name: total_amount
        logicalType: number
        physicalType: numeric
        description: Order total in EUR, excluding VAT, after discounts.
        quality:
          - rule: between
            dimension: accuracy
            severity: error
            mustBe: "[0, 100000]"
      - name: created_at
        logicalType: timestamp
        quality:
          - rule: freshness
            dimension: timeliness
            severity: error
            mustBe: "<= 24h"

Read it as a sentence: the data-platform team promises a table called orders, whose order_id is always present and unique, whose status is one of exactly four values, whose total_amount is a euro figure between zero and one hundred thousand, and which is never more than 24 hours stale. That sentence is the contract. Everything else is syntax.

Contracts, schemas and SLAs

The three are often conflated, and the differences are the whole point.

Data contractSchemaSLA
StatesStructure, meaning, quality and service levelsStructure and types onlyAvailability and timeliness targets
Has two partiesYes — named producer and consumersNoUsually
Versioned as an artefactYes, semantically, in gitSometimesRarely
Machine-enforceableYes, by a validation runnerPartially, at write timeMeasured, not enforced
Covers semanticsYesNoNo

A schema tells you a column is a string. A contract tells you it is a currency code, that it is always one of the ISO 4217 values you accept, that it is never null, and that the payments team is accountable for it. An SLA tells you the table refreshes daily; a contract states that and what has to be true of the rows inside it.

Put simply: a schema is a subset of a contract, and an SLA is a subset of a contract. The contract is the artefact that makes both reviewable in one place.

How contracts get enforced

A contract nobody checks is a document, not an agreement. Enforcement happens at three points.

At review time. Because the contract is a file, a change to it is a diff in a pull request with an author and a reason. This is the most valuable and least discussed property of data contracts: a person can argue with a threshold before it merges. A rule loosened in a vendor UI leaves no trace of whether the business changed or someone got tired of the alert.

At run time. A runner compiles each quality expectation into a query against the actual dataset and records the result. nullCount becomes a COUNT(*) WHERE col IS NULL; freshness becomes a comparison against now(). This is ordinary data validation — the contract is simply where the expectations are declared instead of being scattered across scripts, and the guide to validating data covers how those queries are written, scheduled and thresholded in practice. Severity decides the consequence: error blocks or alerts, warning records a trend.

At change time. Semantic versioning on the contract carries the meaning. A patch loosens or tightens a threshold; a minor adds a column or a rule, additively; a major removes or renames a field, changes a type, or starts rejecting data previously accepted. The version is what lets a consumer decide whether to care about a change without reading the diff.

Catalyst implements exactly this loop: contracts are ODCS YAML with the file as the source of truth, rules compile to engine-native SQL across PostgreSQL, BigQuery, SQL Server and others, and results roll up by quality dimension so coverage gaps are visible rather than implied.

Where to start

Do not begin by contracting everything. Pick one dataset that has broken something recently and has an identifiable owner, write down what it already promises implicitly, and get the producer to agree to it. Import the schema from the database rather than typing it — a generated draft of thirty rules you then prune is a far better starting point than an empty file.

The first contract is a documentation exercise. The second is where the discipline starts paying, because by then someone has tried to make a breaking change and the contract caught it.

Frequently asked questions

What is a data contract in simple terms?

A data contract is a written, versioned agreement about what a dataset guarantees — which fields it has, what they mean, what quality rules they must satisfy, and how fresh the data will be. It is stored in version control like code, reviewed in pull requests, and checked automatically against the real data.

What is the difference between a data contract and a schema?

A schema describes structure — field names and types. A data contract includes the schema but adds the things a schema cannot express: what each field means, the quality rules the values must satisfy, the service levels for freshness and availability, the accountable owner, and a versioning policy that says what counts as a breaking change.

Are data contracts the same as ODCS?

No. "Data contract" is the concept — an agreed, versioned promise between a producer and its consumers. ODCS, the Open Data Contract Standard, is one open, vendor-neutral format for writing that promise down as YAML. You can have data contracts in a homegrown format; using an open standard is what keeps them portable between tools.

How is a data contract enforced?

By compiling its quality expectations into queries that run against the real dataset on a schedule, and by reviewing changes to the contract itself as pull requests. Rules carry a severity, so a violation either blocks downstream consumption or is recorded as a warning trend. Enforcement is ordinary data validation; the contract is just where the expectations are declared.

Who writes the data contract, the producer or the consumer?

The producer owns it, because they are the party making the promise — but the first draft is usually negotiated, since consumers are the ones who know which guarantees they actually depend on. A contract written by consumers alone is a wish list; one written by producers alone tends to promise only what is already easy.