Data Contracts Guide

What is in a data contract: the six sections that carry weight

By the engineer who builds Datadef, from client work on data platforms · Reviewed August 21, 2026

A data contract is a YAML file, versioned next to the code that produces the data, holding six things: who owns this, what the shape is, what the fields mean, what quality is promised, what timeliness is promised, and how much warning consumers get before any of it changes. Everything else in a contract specification is optional. A file missing any of those six is a schema dump with a nicer filename. The format question is now settled, which changes what you should write today.

8 min readFor producers about to write their first contract and unsure what belongs in it

See it as a diagram

Everything below, as a diagram you can edit. Describe yours and see it in seconds.

190/20003 credits left
Try:

No account needed · Editable canvas, not a picture

The six sections, and why each one is load-bearing

Identity and ownership. A stable id, a semantic version, a named owning team, and a contact that reaches a human. Without this, a broken product has no address to escalate to, which is the single most common reason contracts get written at all.

Schema. Fields with types, nullability, primary key, and any enum constraints. This is the part every tool already generates, and it is the least interesting section of the file.

Semantics. What the field means, in the domain vocabulary, including the units and the timezone. The reason status = 3 needs a definition is that six months from now the producer will not remember either.

Quality expectations. The checks that must pass for the data to count as delivered, written as executable checks rather than as prose: row count above zero, no nulls in required columns, no duplicate primary keys, values inside the accepted set.

Service levels. Freshness, retention, and availability as numbers with a measurement point, not adjectives. "Fresh" is not a service level; "no more than two hours behind placed_at, measured at the output port" is.

Terms and lifecycle. Permitted usage, stated limitations, and the date support for this version ends. That last one is the clause that makes the document a contract rather than a description, because it is the only one that constrains the producer's future behaviour rather than describing the data.

Which format, now that there is only one

This used to be a choice and it no longer is. The Data Contract Specification reached 1.2.1 and was then deprecated in favour of the Open Data Contract Standard, which the Bitol project maintains under the Linux Foundation AI and Data umbrella. ODCS v3.1.0 is the current version; tooling support for the older specification is committed through the end of 2026, so migrate on your own schedule rather than under pressure, but write anything new in ODCS.

The shapes differ more than a rename. ODCS has no terms block: permitted usage and limitations live under description, alongside purpose. It has no noticePeriod field either. Its slaProperties is an open list of key/value entries, and the schema says in as many words that there is no limit on the type of properties, so latency, frequency, retention, endOfSupport and endOfLife are conventions carried by the standard example rather than a validated vocabulary. Agree the property names inside your organisation, because the file will not do it for you.

One more field that will bite a migration: slaDefaultElement is deprecated since 3.1 and slated for removal in ODCS 4.0, so put element on each SLA property instead. The contract below is a working ODCS v3.1.0 file that validates cleanly against the standard published JSON schema.

kind: DataContract
apiVersion: v3.1.0
id: checkout-orders
name: Orders
version: 1.2.0
status: active
domain: checkout
dataProduct: orders

description:
  purpose: Confirmed orders, in the checkout domain vocabulary.
  usage: Analytics and reporting. No re-identification of customers.
  limitations: Not for operational lookups; use the checkout API.

team:
  name: checkout-data
  members:
    - username: [email protected]
      role: Owner
      dateIn: "2026-02-01"

support:
  - channel: "#checkout-data"
    tool: slack

servers:
  - server: production
    type: snowflake
    account: acme-prod
    database: analytics
    schema: checkout
    environment: prod

schema:
  - name: orders
    physicalName: orders_fct
    physicalType: table
    description: One row per confirmed order.
    properties:
      - name: order_id
        logicalType: string
        physicalType: varchar(36)
        primaryKey: true
        primaryKeyPosition: 1
        required: true
        description: Checkout order id, stable across retries.
        quality:
          - metric: duplicateValues
            mustBe: 0
            dimension: uniqueness
            severity: error
      - name: placed_at
        logicalType: date
        physicalType: timestamp_ntz
        required: true
        description: Confirmation time, UTC.
      - name: status
        logicalType: string
        physicalType: varchar(16)
        required: true
        description: One of placed, paid, shipped, cancelled.
      - name: total_amount
        logicalType: number
        physicalType: decimal(12,2)
        description: Order total in currency_code, not minor units.
    quality:
      - metric: rowCount
        mustBeGreaterThan: 0
        dimension: completeness
        severity: error

slaProperties:
  - property: latency
    value: 2
    unit: h
    element: orders.placed_at
    driver: analytics
  - property: frequency
    value: 1
    unit: d
    element: orders.placed_at
  - property: retention
    value: 3
    unit: y
    element: orders.placed_at
  - property: endOfSupport
    value: "2027-06-30T00:00:00Z"

Who writes it, who signs it, where it lives

The producer writes it and owns it, because the producer controls the schema, the semantics, and the generation logic. The main consumers co-author the first version, because they are the ones who know which fields carry business impact and which quality failures actually break a dashboard. A contract written by the producer alone tends to promise the easy things; one written by consumers alone tends to promise things the producer cannot keep.

It lives in the producer's repository, next to the transformation code, and it moves through the same pull request. Storing contracts in a separate governance repository sounds tidy and guarantees they fall out of step, because the schema change and the contract change stop being the same commit.

Version it semantically. Adding an optional field is a minor version. Removing a field, retyping one, or tightening an enum is a major version, and the notice period starts when the major version is announced, not when it ships.

Making the contract fail the build

A contract nothing checks is documentation. The enforcement loop has two halves: a static comparison of the new contract against the version on the main branch, so a breaking change is visible in review, and a live check that the data satisfies the schema and quality blocks. The first runs on every pull request; the second runs after every load.

The Data Contract CLI covers both from the same file. Two notes on its current surface, because a lot of published examples predate them. The command that reports differences between two versions is changelog, taking both files as arguments; there is no breaking subcommand in the current CLI, whose top-level commands are init, edit, lint, changelog, test, ci, export, dbt, import, catalog, publish and api. And datacontract dbt sync generates dbt tests from the contract into an existing project, which is the cheapest way to stop maintaining the quality rules twice.

# 1. Is the file itself valid ODCS?
datacontract lint orders.odcs.yaml

# 2. What changed against main? Read this in review before merging.
git show origin/main:contracts/checkout/orders.odcs.yaml > /tmp/prev.yaml
datacontract changelog /tmp/prev.yaml orders.odcs.yaml

# 3. Does the live data satisfy schema and quality? After each load.
datacontract test orders.odcs.yaml

# 4. Optional: push the quality rules into the dbt project instead of
#    maintaining them twice, then run them where the models run.
datacontract dbt sync orders.odcs.yaml --project-dir ./warehouse
datacontract dbt test orders.odcs.yaml --project-dir ./warehouse

Where the contract meets the picture

Contracts describe one product; a diagram shows which products feed which. Keeping both generated from the repository is what stops the two from disagreeing. See keeping data documentation in sync.

FAQ

What does a data contract contain?

Six sections: identity and ownership (id, version, owning team, contact), schema (fields, types, nullability, keys), semantics (what each field means, units, timezone), quality expectations as executable checks, service levels for freshness and retention, and terms including the notice period before a breaking change.

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

The producer writes and owns it, because the producer controls the schema, the semantics, and the generation logic. The main consumers co-author the first version, since they know which fields carry business impact. Producer-only contracts tend to promise only the easy things; consumer-only ones promise what the producer cannot keep.

Where should data contracts be stored?

In the producer repository, beside the transformation code, so a schema change and its contract change travel in the same pull request. Keeping contracts in a separate governance repository looks tidier and reliably lets them drift out of step with the code. It also makes the review diff useful: comparing the incoming file against the one on main is what surfaces a breaking change before it merges.

What is a notice period in a data contract?

The minimum warning a producer gives before a breaking change takes effect. It is the clause that constrains the producer rather than describing the data, which is what makes the document a contract instead of a schema description. The Open Data Contract Standard has no dedicated field for it: teams express it as an endOfSupport date in slaProperties, whose property names are an open list rather than a fixed vocabulary.

Which data contract specification should you use?

The Open Data Contract Standard, currently v3.1.0, maintained by the Bitol project under the Linux Foundation AI and Data umbrella. The older Data Contract Specification reached 1.2.1 and was deprecated in its favour, with tooling support committed through the end of 2026. Write anything new in ODCS and migrate existing files on your own schedule.

How do you version a data contract?

Semantically, against the contract rather than the code. Adding an optional field is a minor version. Removing a field, changing a type, or tightening an enum is a major version, published alongside the previous one for the notice period, with the clock starting at announcement rather than at release.