The dashboard broke again. Not because the warehouse disappeared, but because a producer renamed customer_id to cust_id, widened a timestamp, and started sending nulls in a field marketing still treats as required. Everyone is acting in good faith. Nobody agreed, in writing, what “good” means for that feed. That missing agreement is what people now call a data contract.
This Key Term deep dive explains data contracts in plain language for analysts, analytics engineers, and product-minded data folks. You will see what belongs in a contract, what does not, a filled example, and how contracts connect to quality, pipelines, and stewardship without turning into a binder nobody reads.
What you’ll learn
- A practical definition of a data contract (producer and consumer lens)
- Core sections: schema, semantics, quality, delivery, ownership, change policy
- How contracts differ from catalogs, SLAs, and informal Slack norms
- A worked example for an orders feed and how to enforce pieces of it
- Common failure modes and a small first contract you can draft this week
Definition in one breath
A data contract is an explicit, versioned agreement between the team that produces a dataset (or event stream) and the teams that consume it. The agreement covers structure, meaning, quality expectations, delivery timing, ownership, and how breaking changes are introduced. If it is only a schema file with no owners and no change rules, it is a partial contract at best.
Rule of thumb: If a producer can change a field tomorrow and consumers only learn when dashboards break, you do not have a contract. You have hope.
Contracts are popular in analytics engineering and platform circles because warehouses made it easy to share tables and hard to share guarantees. They are not magic. They are interface design for data.
Why contracts showed up
Classic enterprise integration had heavy interface specs. Modern analytics often had the opposite: dump tables into a lake, document later, fix forward. That works until:
- Multiple consumers depend on the same feed for money, compliance, or exec metrics
- Producers ship product changes weekly and treat analytics as a side effect
- AI features and reverse ETL make bad fields travel farther, faster
- On-call for “why is revenue null?” becomes a lifestyle
Contracts push compatibility checks left: before merge, before deploy, before the board meeting. They pair naturally with checks from the data quality series and ownership habits from the data stewardship series.
Producer vs consumer: two jobs, one document

Producers generate the data: application services, operational databases, event pipelines, third-party extracts. They care about shipping product features without infinite freeze.
Consumers use the data: analytics, finance, ML features, reverse ETL, other microservices. They care about stable meaning and timely delivery.
A contract is the negotiation artifact. Producers accept some constraints. Consumers accept some residual risk and versioning rules. Neither side gets unbounded freedom.
What goes in a data contract
Formats vary (YAML, JSON, Open Data Contract Standard-inspired docs, internal wiki plus schema registry). Content should cover the same bones.
1. Identity and ownership
- Dataset or stream name, domain, environment (prod/stage)
- Producer team and on-call path
- Primary consumer stewards (not “everyone with a warehouse login”)
- Version number and last review date
2. Schema
- Field names, types, nullability
- Primary keys and important foreign keys
- Allowed enums or coded values
- Compatibility policy (backward compatible additions vs breaking renames)
3. Semantics (meaning)
- What one row or event represents (grain)
- Business definitions for critical fields
- Timezone and unit conventions (cents vs dollars, UTC vs local)
- Known caveats (late events, partial backfills)
4. Quality expectations
- Freshness targets (e.g. data available by 06:00 local)
- Completeness thresholds for required fields
- Uniqueness of keys
- Acceptable ranges or referential checks
- What happens when checks fail (page producer, quarantine, continue with flag)
5. Delivery and interface
- Location (table, topic, file path, API)
- Update pattern (batch daily, microbatch, streaming)
- Partitioning and retention
- Security and PII classification
6. Change management
- How consumers are notified
- Deprecation windows for breaking changes
- Dual-publish periods when renaming fields
- Who can approve exceptions
If your head is buzzing, start with identity, schema, grain, and two quality checks. Expand when pain appears. Perfect contracts that never ship help nobody.
Contracts vs related ideas
| Concept | Main job | Not the same as a contract because… |
|---|---|---|
| Data catalog | Discover and document assets | Docs can lag; may lack enforcement and owners who answer |
| Schema registry | Store and evolve schemas | Types without business meaning or freshness promises |
| SLA / SLO | Service reliability targets | Often uptime-focused; may ignore field semantics |
| dbt tests | Assert conditions in transforms | Powerful enforcement tool; not the full social agreement |
| API OpenAPI spec | HTTP interface contract | Closest cousin; data contracts extend the idea to tables/streams |
Think of the contract as the product requirements for a dataset. Tests and registries are implementation gears. Catalogs are the storefront. You usually want more than one gear, not a single buzzword.
Worked example: orders_v1 contract
Imagine a commerce platform team produces analytics.orders_v1 nightly for finance and growth analytics.

A readable contract excerpt (YAML-inspired, simplified):
dataset: analytics.orders_v1
version: 1.4.0
domain: commerce
producer:
team: checkout-platform
contact: checkout-oncall
consumers:
- finance-analytics
- growth-analytics
grain: One row per order_id for completed checkout attempts that created an order
schema:
- name: order_id
type: string
required: true
unique: true
- name: customer_id
type: string
required: true
- name: order_ts_utc
type: timestamp
required: true
description: Order creation time in UTC
- name: currency
type: string
required: true
allowed: ["USD", "EUR", "GBP"]
- name: amount_cents
type: integer
required: true
description: Gross merchandise value in minor units before tax
- name: status
type: string
required: true
allowed: ["paid", "pending", "cancelled", "refunded"]
quality:
freshness:
max_delay_hours: 6
ready_by_local: "06:00 America/New_York"
checks:
- type: not_null
fields: [order_id, customer_id, order_ts_utc, amount_cents]
- type: unique
fields: [order_id]
- type: range
field: amount_cents
min: 0
delivery:
location: warehouse.analytics.orders_v1
mode: batch_nightly
pii: customer_id is indirect identifier; join to customers under policy
change_policy:
additive_fields: allowed with notice in #data-changes
breaking_changes: 30 day deprecation; dual publish when renaming
approvals: producer lead + one primary consumer stewardNotice what this prevents. Renaming amount_cents without dual publish is a breaking change. Sending negative amounts fails a range check. Shipping at noon without a declared exception violates freshness. None of that requires a 100-page governance program. It requires written expectations and a place to enforce a subset of them.
Enforcement sketch
Producers might validate events before load. Consumers might add warehouse tests. Example SQL checks consumers run after load:
-- uniqueness
SELECT order_id, COUNT(*) AS n
FROM analytics.orders_v1
GROUP BY 1
HAVING COUNT(*) > 1;
-- nulls on required fields
SELECT
COUNT(*) AS rows,
COUNT(order_id) AS order_id_nonnull,
COUNT(customer_id) AS customer_id_nonnull,
COUNT(amount_cents) AS amount_nonnull
FROM analytics.orders_v1
WHERE order_ts_utc >= CURRENT_DATE - INTERVAL '1 day';
-- freshness proxy: max timestamp should be recent for daily batch
SELECT MAX(order_ts_utc) AS max_order_ts
FROM analytics.orders_v1;Schema-diff tooling in CI can block a PR that removes customer_id without a version bump. The social part remains: someone still has to care when the check goes red. Contracts without response paths become unread PDFs.
How contracts connect to metrics and pipelines
A metric definition (“net revenue excludes pending”) sits on top of reliable inputs. If status values drift, the metric contract cannot save you. Conversely, a perfect table contract does not define the business metric. You need both layers.
Pipeline stages should know which outputs are contracted. Landing-zone raw dumps can be messier. Serving tables used by finance should be stricter. That is interface design along the path, not a demand that every raw topic be perfect. For pipeline thinking, see the data pipelines series. For metric definitions, see the metrics series.
When you need a contract (and when you do not)
Strong candidates:
- Shared sources for certified executive metrics
- Feeds powering money movement, compliance, or customer communications
- Cross-team interfaces that already break quarterly
- ML features that silently skew when null rates climb
Weak candidates (at least at first):
- One-off extracts for a single analyst experiment
- Throwaway sandbox tables
- Rapid product prototypes with one consumer who sits next to the producer
Start where outages hurt. Expand when the pain migrates. For learning paths around these skills, browse Learn.
Common mistakes
- Schema-only contracts: types without grain and meaning still produce wrong metrics.
- No owners: a YAML file cannot attend the incident call.
- Unbounded consumers: if everyone is a consumer, nobody prioritizes changes.
- Unenforceable freshness theater: promising 30-second latency on a nightly batch job.
- Breaking changes without dual publish: renames that nuke historical dashboards.
- Contracts as blame weapons: the goal is fewer surprises, not courtroom energy.
- Boiling the ocean: 200 fields polished while the three money fields stay wild.
- Ignoring PII: delivery location and access rules belong in the agreement.
Practice: draft a thin contract in 40 minutes
Pick one table or feed that already causes tickets.
- Write the grain sentence.
- List the five most important fields with types and nullability.
- Name producer and one primary consumer steward.
- Add two quality checks you could run this week.
- Write a one-paragraph change policy (how breaking changes are announced).
- Share the draft with the producer team as a conversation starter, not a surprise mandate.
If they push back, negotiate scope. A thinner contract that both sides accept beats a perfect document that lives alone in a drive folder.
Quick recap
- A data contract is a versioned producer-consumer agreement on structure, meaning, quality, delivery, and change.
- Schema alone is not enough; grain and semantics carry the business truth.
- Owners and change policy make the document operational.
- Enforce what you can in CI and warehouse tests; staff the rest with clear response paths.
- Start with high-pain shared feeds. Expand with evidence, not fashion.
- Contracts complement catalogs, SLAs, and metric definitions; they do not replace them.
Sources
- Open Data Contract Standard (community effort toward portable contract structure): https://bitol-io.github.io/open-data-contract-standard/
- Apache Avro specification (schema evolution concepts widely used in data interfaces): https://avro.apache.org/docs/current/specification/
- Confluent Schema Registry documentation (compatibility modes for evolving schemas): https://docs.confluent.io/platform/current/schema-registry/index.html
- dbt Labs, data tests documentation (enforcement patterns in analytics engineering): https://docs.getdbt.com/docs/build/data-tests
- DAMA International, DMBOK resources (governance and stewardship context for agreements): https://www.dama.org/cpages/body-of-knowledge
- Google SRE Workbook / SRE Book material on SLOs (useful analogy for freshness and reliability targets): https://sre.google/books/
