Apicurio Registry data contracts
This chapter introduces the Data Contracts framework in Apicurio Registry, which uses the Open Data Contract Standard (ODCS) v3.1 as the native contract format for managing schema contracts between data producers and consumers.
Overview
The Data Contracts framework enables teams to define, enforce, and track formal agreements about schema structure, ownership, and quality using the industry-standard ODCS format.
A data contract in Apicurio Registry combines:
-
ODCS format - Contracts are submitted as ODCS v3.1 YAML documents and projected onto schema artifacts
-
Metadata - Ownership, classification, SLA, and support information projected as labels
-
Lifecycle - Status tracking (DRAFT, STABLE, DEPRECATED) and promotion stages (DEV, STAGE, PROD)
-
Quality rules - Accuracy and completeness rules from ODCS projected as CEL contract rules
-
Field tags - PII and classification annotations stored as version labels, with automatic extraction from Avro, JSON Schema, and Protobuf schemas
-
Multi-contract support - Multiple contracts can reference the same schema artifact without overwriting each other
| Data contracts are always available. No special configuration is required — submitting a contract is an explicit opt-in action. |
ODCS native format
Apicurio Registry uses the Open Data Contract Standard (ODCS) v3.1 as its native contract format. ODCS is a Linux Foundation standard maintained by the Bitol project. When an ODCS contract is submitted, its contents are projected onto the referenced schema artifacts.
Why ODCS
-
Industry standard - ODCS is the Linux Foundation standard for data contracts, with ecosystem tooling such as
datacontract-cli -
Vendor neutral - Apicurio Registry is the first schema registry to natively support ODCS
-
Interoperable - ODCS contracts can be imported/exported and used with external tools without translation
Projection model
When you submit an ODCS contract, Apicurio Registry parses the YAML, stores it as an ODCS_CONTRACT artifact, and projects its contents onto the referenced schema artifacts. Each projection is namespaced by the contract ID, so multiple contracts can coexist on the same schema.
| ODCS section | Projected to |
|---|---|
|
|
|
|
|
|
|
CEL contract rules prefixed |
|
|
|
|
|
|
|
|
Multi-contract support
Multiple ODCS contracts can reference the same schema artifact. Each contract’s projections are namespaced by contract ID, so they do not interfere:
Contract "orders-contract" → contract.orders-contract.owner.team = orders-team Contract "billing-contract" → contract.billing-contract.owner.team = billing-team
Updating or deleting a contract only affects its own namespaced labels, rules, and tags.
ODCS contract example
apiVersion: v3.1.0
kind: DataContract
id: orders-contract
info:
title: Orders Contract
version: 1.0.0
description: Data contract for order events
status: active
dataClassification: confidential
team:
name: orders-team
domain: commerce
contact: orders@company.com
schemas:
- name: OrderEvent
type: avro
location: orders/OrderEvent:3
fields:
customerEmail:
description: Customer email address
pii: true
tags:
- PII
- EMAIL
totalAmount:
description: Total order amount in cents
quality:
accuracy:
- name: positive-amount
expression: totalAmount > 0
threshold: 1.0
- name: valid-email
expression: "customerEmail.matches('.*@.*\\\\..*')"
threshold: 0.99
completeness:
- field: orderId
threshold: 1.0
- field: customerEmail
threshold: 0.99
freshness:
maxStaleness: PT5M
serviceLevel:
availability: 0.999
latency:
p99: PT1S
When submitted, this contract:
-
Creates an
ODCS_CONTRACTartifact storing the YAML -
Sets namespaced labels on the
orders/OrderEventartifact:contract.orders-contract.status=STABLE,contract.orders-contract.owner.team=orders-team,contract.orders-contract.classification=CONFIDENTIAL -
Creates two CEL contract rules (
odcs:orders-contract:positive-amountwith onFailure=ERROR,odcs:orders-contract:valid-emailwith onFailure=DLQ) -
Stores completeness thresholds as labels (
contract.orders-contract.quality.completeness.orderId=1.0) -
Sets field tags:
field-tag.orders-contract:customerEmail|PII=EXTERNAL,field-tag.orders-contract:customerEmail|EMAIL=EXTERNAL -
Stores SLA labels for external monitoring tools
Quality rule threshold mapping
ODCS accuracy rule thresholds determine what happens when a rule fails:
-
1.0 (100%) →
ERROR- Zero tolerance, reject non-compliant data -
0.95-0.99 →
DLQ- Near-100%, route violations to dead letter queue -
Below 0.95 →
NONE- Soft constraint, log only
Manual rules are preserved
When you update an ODCS contract, only rules prefixed with odcs:{contractId}: are replaced. Rules added manually through the contract ruleset API are preserved.
Schema location format
The schemas[].location field references an artifact in the registry:
groupId/artifactId:version
Examples:
-
orders/OrderEvent:3- group "orders", artifact "OrderEvent", version "3" -
OrderEvent:latest- default group, artifact "OrderEvent", latest version -
OrderEvent- default group, artifact "OrderEvent", latest version
Field-level tags
Field tags provide semantic annotation of schema fields, enabling you to identify sensitive data and apply tag-based governance.
Tag sources
Tags come from two sources:
-
Inline tags - Extracted automatically from schema content during registration by tag extractors
-
External tags - Projected from ODCS contracts or set manually via the version labels API
Supported schema formats
Tag extractors are available for the following formats:
Avro
Tags are extracted from tags and confluent:tags field properties:
{
"type": "record",
"name": "User",
"fields": [{
"name": "email",
"type": "string",
"tags": ["PII", "EMAIL"]
}]
}
Contract rules
Contract rules define validation and transformation logic associated with artifacts or specific versions. Rules are organized into domain rules (business logic) and migration rules (schema evolution).
When you submit an ODCS contract, its quality.accuracy rules are automatically projected as CEL domain rules on the referenced schema artifact.
Contract rules are enforced both server-side (via the rule execution REST endpoint) and client-side (via Java Kafka SerDes integration). See Kafka SerDes integration for SerDes configuration.
Rule modes
-
WRITE - Execute on serialize (producer-side)
-
READ - Execute on deserialize (consumer-side)
-
WRITEREAD - Execute on both
-
UPGRADE / DOWNGRADE - Execute during schema migration
Rule types
-
CEL - Common Expression Language for validation conditions and simple transforms. Supports full CEL spec including
has(),size(),contains(),startsWith(), parenthesized grouping, and ternary expressions. -
JSONATA - JSONata expressions for data transformation. Supports both CONDITION (truthy check) and TRANSFORM (returns transformed data) kinds. Useful for schema migration transforms.
Contract lifecycle
The contract lifecycle tracks the maturity of a schema through defined status transitions.
Statuses
-
DRAFT - The schema is being developed and is not ready for production
-
STABLE - The schema is production-ready
-
DEPRECATED - The schema is being phased out
Schema migration
Schema migration allows you to transform data records between different schema versions using migration rules.
Migration rules
Migration rules use the UPGRADE or DOWNGRADE mode and are typically JSONata TRANSFORM rules that reshape data from one version to another:
{
"domainRules": [],
"migrationRules": [
{
"name": "v1-to-v2-upgrade",
"kind": "TRANSFORM",
"type": "JSONATA",
"mode": "UPGRADE",
"expr": "$ ~> |$|{'fullName': firstName & ' ' & lastName}|",
"onFailure": "ERROR",
"disabled": false
}
]
}
Migration endpoint
The migration endpoint chains transforms across multiple versions:
curl -X POST \
http://localhost:8080/apis/registry/v3/groups/my-group/artifacts/my-artifact/contract/migrate \
-H 'Content-Type: application/json' \
-d '{
"sourceVersion": "1",
"targetVersion": "3",
"record": {"firstName": "John", "lastName": "Doe", "amount": 100}
}'
The service determines the direction (upgrade or downgrade), loads migration rules for each intermediate version, and chains the transforms sequentially.
Compatibility groups
Compatibility groups partition the version history for compatibility checking. Versions in different groups are not compared against each other during compatibility validation.
# Set compatibility group
curl -X PUT \
http://localhost:8080/apis/registry/v3/groups/my-group/artifacts/my-artifact/contract/compatibility-group \
-H 'Content-Type: application/json' \
-d '{"group": "v2-family"}'
# Get compatibility group
curl http://localhost:8080/apis/registry/v3/groups/my-group/artifacts/my-artifact/contract/compatibility-group
Kafka SerDes integration
Contract rules can be enforced during normal Kafka serialization and deserialization using the Apicurio Registry SerDes libraries. Rules are executed transparently — no code changes are required beyond configuration.
Configuration
Enable contract rule enforcement by setting these properties on your Kafka producer or consumer:
| Property | Default | Description |
|---|---|---|
|
|
Enable contract rule execution during serialize/deserialize |
|
|
When |
How it works
-
Producer (serializer): After schema resolution and before serialization, the serializer calls the server-side rule execution endpoint with mode
WRITE. If any CONDITION rule withonFailure=ERRORfails, serialization is rejected. -
Consumer (deserializer): After deserialization, the deserializer calls the rule execution endpoint with mode
READ. This enables read-side validation rules. -
DLQ pattern: Set
fail-on-error=falseto allow serialization to proceed even when rules fail. Your application can inspect the violations and route the message to a dead letter queue.
Example: Producer with contract rules
Properties props = new Properties();
props.put(SerdeConfig.REGISTRY_URL, "http://localhost:8080/apis/registry/v3");
props.put(SerdeConfig.EXPLICIT_ARTIFACT_GROUP_ID, "my-group");
props.put(SerdeConfig.ARTIFACT_RESOLVER_STRATEGY, TopicRecordIdStrategy.class.getName());
props.put(SerdeConfig.CONTRACT_RULES_ENABLED, "true");
props.put(SerdeConfig.CONTRACT_RULES_FAIL_ON_ERROR, "true");
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, AvroKafkaSerializer.class.getName());
// ... other Kafka producer properties
KafkaProducer<String, GenericRecord> producer = new KafkaProducer<>(props);
Audit log
The contract audit log records all contract operations for compliance and debugging.
Audited operations
-
Metadata updates (owner, classification, status changes)
-
Ruleset configuration (create, update, delete)
-
Status transitions (DRAFT → STABLE → DEPRECATED)
-
Contract submission and re-projection
Querying the audit log
curl http://localhost:8080/apis/registry/v3/groups/my-group/artifacts/my-artifact/contract/audit
[
{
"action": "STATUS_TRANSITION",
"detail": "Status changed from DRAFT to STABLE",
"userId": "admin",
"createdOn": "2026-05-19T10:30:00Z"
},
{
"action": "RULESET_CONFIGURED",
"detail": "Contract ruleset updated",
"userId": "admin",
"createdOn": "2026-05-19T10:25:00Z"
}
]
Contract search
Search for contracts across the registry using metadata filters.
Search parameters
| Parameter | Description |
|---|---|
|
Filter by lifecycle status (DRAFT, STABLE, DEPRECATED) |
|
Filter by owner team name |
|
Filter by data classification (e.g., CONFIDENTIAL, PUBLIC) |
|
Filter by group |
|
Filter by artifact |
|
Pagination (default limit: 20, max: 500) |
|
Sort by field and direction |
Global contract rules
Global contract rulesets provide a baseline set of rules applied to all artifacts. Artifact-level and version-level rules override global rules by name.
Rule precedence
When rules are executed, they are merged from three scopes:
-
Global rules — apply to all artifacts (set via
/admin/contracts/ruleset) -
Artifact rules — apply to a specific artifact (set via
/groups/{g}/artifacts/{a}/contract/ruleset) -
Version rules — apply to a specific version (set via
…/versions/{v}/contract/ruleset)
If a rule with the same name exists at multiple levels, the most specific scope wins.
Managing global rules
# Set global ruleset
curl -X PUT \
http://localhost:8080/apis/registry/v3/admin/contracts/ruleset \
-H 'Content-Type: application/json' \
-d '{
"domainRules": [{
"name": "require-non-empty",
"kind": "CONDITION",
"type": "CEL",
"mode": "WRITE",
"expr": "size(record) > 0",
"onFailure": "ERROR",
"disabled": false
}],
"migrationRules": []
}'
# Get global ruleset
curl http://localhost:8080/apis/registry/v3/admin/contracts/ruleset
# Delete global ruleset
curl -X DELETE http://localhost:8080/apis/registry/v3/admin/contracts/ruleset
REST API
The Data Contracts REST API provides endpoints for managing ODCS contracts, contract metadata, and contract rulesets.
ODCS contract endpoints
| Method | Endpoint | Description |
|---|---|---|
POST |
|
Submit an ODCS contract YAML. Creates the contract artifact and projects onto referenced schema artifacts. |
GET |
|
List ODCS contracts in a group (paginated, default 20, max 500) |
GET |
|
Retrieve the ODCS contract YAML |
PUT |
|
Update an ODCS contract (creates a new version and re-projects) |
DELETE |
|
Delete an ODCS contract artifact |
GET |
|
Export an artifact’s contract data as ODCS YAML |
Contract metadata endpoints
| Method | Endpoint | Description |
|---|---|---|
GET |
|
Get contract metadata for an artifact |
PUT |
|
Create or update contract metadata |
Contract ruleset endpoints
| Method | Endpoint | Description |
|---|---|---|
GET |
|
Get the contract ruleset for an artifact |
PUT |
|
Create or replace the contract ruleset |
DELETE |
|
Delete the contract ruleset |
Version-level rulesets are also available at …/versions/2024.Q2/contract/ruleset.
Contract status transition endpoint
| Method | Endpoint | Description |
|---|---|---|
POST |
|
Change lifecycle status with transition validation |
Governance and quality endpoints
| Method | Endpoint | Description |
|---|---|---|
POST |
|
Promote contract stage (DEV → STAGE → PROD). Body: |
GET |
|
Get quality score (overall, completeness, compliance, stability) |
Rule execution and migration endpoints
| Method | Endpoint | Description |
|---|---|---|
POST |
|
Execute contract rules against a data record. Body: |
POST |
|
Migrate a record between versions. Body: |
Audit log endpoint
| Method | Endpoint | Description |
|---|---|---|
GET |
|
Get contract audit log entries for an artifact |
Compatibility group endpoints
| Method | Endpoint | Description |
|---|---|---|
GET |
|
Get the compatibility group for an artifact |
PUT |
|
Set the compatibility group for an artifact |
Contract search endpoint
| Method | Endpoint | Description |
|---|---|---|
GET |
|
Search contracts across the registry |
Global contract ruleset endpoints
| Method | Endpoint | Description |
|---|---|---|
GET |
|
Get the global contract ruleset |
PUT |
|
Set the global contract ruleset |
DELETE |
|
Delete the global contract ruleset |
Uses real CEL (Common Expression Language) via the cel-standalone library. Full CEL spec support including parenthesized grouping, ternary expressions, string functions, has(), size(). JSONata is also supported for data transformation rules.
Example: Submitting an ODCS contract
curl -X POST \
http://localhost:8080/apis/registry/v3/groups/my-group/contracts \
-H 'Content-Type: application/x-yaml' \
--data-binary @my-contract.yaml
{
"contractId": "orders-contract",
"version": "1.0.0",
"projection": {
"rulesApplied": 2,
"labelsApplied": 8,
"tagsApplied": 3,
"warnings": []
}
}
Example: Listing contracts
curl "http://localhost:8080/apis/registry/v3/groups/my-group/contracts?limit=10&offset=0"
Example: Exporting as ODCS
curl http://localhost:8080/apis/registry/v3/groups/my-group/artifacts/my-artifact/contract/export
Returns the reconstructed ODCS v3.1 YAML from the artifact’s current contract labels, rules, and field tags.
Example: Promoting a contract
curl -X POST \
http://localhost:8080/apis/registry/v3/groups/my-group/artifacts/my-artifact/contract/promote \
-H 'Content-Type: application/json' \
-d '{"contractId": "orders-contract", "targetStage": "STAGE"}'
Example: Getting quality score
curl "http://localhost:8080/apis/registry/v3/groups/my-group/artifacts/my-artifact/contract/quality?contractId=orders-contract"
{"overall": 0.85, "completeness": 0.83, "compliance": 1.0, "stability": 0.67}
Example: Executing contract rules
curl -X POST \
http://localhost:8080/apis/registry/v3/groups/my-group/artifacts/my-artifact/versions/1/contract/execute \
-H 'Content-Type: application/json' \
-d '{"mode": "WRITE", "record": {"message": {"totalAmount": 100, "status": "active"}}}'
{"passed": true, "violations": [], "executedRules": 2, "failedRules": 0}
Example: Setting contract metadata directly
curl -X PUT \
http://localhost:8080/apis/registry/v3/groups/my-group/artifacts/my-artifact/contract/metadata \
-H 'Content-Type: application/json' \
-d '{
"status": "DRAFT",
"ownerTeam": "Platform Team",
"ownerDomain": "payments",
"supportContact": "platform@example.com",
"classification": "CONFIDENTIAL",
"stage": "DEV"
}'
Configuration
Data contracts are always available — no special configuration is required. Submitting a contract is an explicit opt-in action that does not affect existing registry functionality.
SerDes configuration
| Property | Default | Description |
|---|---|---|
|
|
Enable contract rule execution during serialize/deserialize |
|
|
When |
Storage compatibility
Data contracts work across all storage variants:
-
SQL (PostgreSQL) — Full support with immediate consistency. The
contract_audit_logtable is created automatically (DB upgrade 106). -
KafkaSQL — Full support; label writes go through the Kafka journal for multi-node consistency
-
GitOps / KubernetesOps — Read-only safe; write operations are rejected by the read-only decorator
