Apicurio Registry data contracts
This chapter introduces the Data Contracts framework in Apicurio Registry, which provides comprehensive support 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. Data contracts help ensure data producers and consumers adhere to clearly defined standards throughout the schema lifecycle.
A data contract in Apicurio Registry combines:
-
Metadata - Standardized ownership, classification, and support information
-
Lifecycle - Status tracking (DRAFT, STABLE, DEPRECATED) and promotion workflows
-
Rules - Contract rulesets with domain and migration rules, managed through the REST API
-
Field Tags - Semantic annotation of schema fields (for example, PII, SENSITIVE, EMAIL) (planned)
-
Migration - Schema evolution with data transformation between versions (planned)
Data contracts is an experimental feature. You must set apicurio.features.experimental.enabled=true and apicurio.contracts.enabled=true to use contract endpoints.
|
Contract metadata
Contract metadata provides standardized information about schema ownership, classification, and support. Metadata is stored using the reserved contract.* label namespace.
Metadata fields
The following metadata fields are available for data contracts:
| Field | Type | Description |
|---|---|---|
|
Enum |
Contract lifecycle status: |
|
String |
Name of the team responsible for the contract |
|
String |
Business domain (for example, payments, orders, users) |
|
String |
Support email address |
|
Enum |
Data classification: |
|
Enum |
Promotion stage: |
|
ISO-8601 date |
Date when the contract status became STABLE |
|
ISO-8601 date |
Date when the contract status became DEPRECATED |
|
String |
Reason for deprecating the contract |
Data classification levels
Data classification helps teams understand the sensitivity of data in a contract:
-
PUBLIC - No restrictions, data can be freely shared
-
INTERNAL - For internal use only, not for external parties
-
CONFIDENTIAL - Need-to-know basis, limited access
-
RESTRICTED - Highly sensitive data requiring strict access controls
Contract lifecycle
The contract lifecycle tracks the maturity and availability of a schema through defined status transitions.
Lifecycle statuses
-
DRAFT - Initial state. The schema is being developed and is not ready for production use.
-
STABLE - The schema is production-ready and consumers can rely on it.
-
DEPRECATED - The schema is being phased out. Consumers should migrate to a newer version.
Status transitions
The following status transitions are allowed:
DRAFT ──────► STABLE ──────► DEPRECATED
│ ▲
└──────────────────────────────┘
(skip stable)
-
DRAFT→STABLE- Use when the schema is ready for production -
STABLE→DEPRECATED- Use when phasing out the schema -
DRAFT→DEPRECATED- Use to deprecate without ever reaching stable
Reverse transitions (for example, STABLE → DRAFT or DEPRECATED → STABLE) are not allowed.
|
Promotion workflow (planned)
| Automated promotion workflow enforcement is planned for a future release. The promotion stage label can be set manually. |
The promotion workflow tracks which environment a contract is deployed to:
DEV ──────► STAGE ──────► PROD
-
DEV - Development environment
-
STAGE - Staging or QA environment
-
PROD - Production environment
Promotion rules:
-
You can only promote to the next stage (DEV → STAGE → PROD)
-
You cannot skip stages (DEV → PROD is not allowed)
-
You cannot demote (PROD → STAGE is not allowed)
-
PROD promotion might require STABLE status (configurable)
Field-level tags (planned)
| Field-level tags are planned for a future release and are not yet implemented. |
Field tags provide semantic annotation of schema fields, enabling you to identify sensitive data, apply tag-based rules, and search for artifacts by tag.
Supported tag formats
Tags can be embedded directly in schema definitions:
{
"type": "record",
"name": "User",
"fields": [{
"name": "email",
"type": "string",
"tags": ["PII", "EMAIL"]
}, {
"name": "ssn",
"type": "string",
"tags": ["PII", "SENSITIVE"]
}]
}
{
"type": "object",
"properties": {
"email": {
"type": "string",
"x-tags": ["PII", "EMAIL"]
}
}
}
message User {
string email = 1 [(apicurio.field_meta).tags = "PII,EMAIL"];
}
Contract rules
Contract rules enable you to define validation and transformation logic that can be associated with artifacts or specific versions. Rules are stored in the registry and organized into rulesets containing two categories: domain rules and migration rules.
| Contract rules are currently stored and managed through the REST API. Runtime execution of rules in SerDes (serializers/deserializers) is planned for a future release. |
Rulesets
A contract ruleset groups rules into two categories:
-
Domain rules - Validation and transformation rules for business logic (for example, field validation, data encryption)
-
Migration rules - Rules for schema version migration (for example, upgrading or downgrading data between schema versions)
Rulesets can be defined at two levels:
-
Artifact-level - Apply to all versions of an artifact
-
Version-level - Apply to a specific version only
Artifact-level and version-level rulesets are independent. Deleting an artifact-level ruleset does not affect version-level rulesets, and vice versa.
Rule kinds
Each rule has a kind that defines its purpose:
-
CONDITION - A validation rule that checks a condition and passes or fails
-
TRANSFORM - A transformation rule that modifies data
Rule modes
The mode defines when a rule is applied:
-
WRITE - Execute on serialize (producer-side)
-
READ - Execute on deserialize (consumer-side)
-
WRITEREAD - Execute on both serialize and deserialize
-
UPGRADE - Execute during schema upgrade migration
-
DOWNGRADE - Execute during schema downgrade migration
Rule actions
When a rule succeeds or fails, you can configure the action:
-
NONE - Continue processing (log only)
-
ERROR - Throw an exception and reject the operation
-
DLQ - Route the message to a dead letter queue
Example: Validation rule
A validation rule using CEL (Common Expression Language) to check a condition:
{
"name": "validateAge",
"kind": "CONDITION",
"type": "CEL",
"mode": "WRITE",
"expr": "message.age >= 0 && message.age <= 150",
"onFailure": "ERROR"
}
Example: Transform rule
A transformation rule to encrypt PII fields:
{
"name": "encryptPII",
"kind": "TRANSFORM",
"type": "ENCRYPT",
"mode": "WRITE",
"tags": ["PII"],
"params": {
"encrypt.kek.name": "pii-key"
},
"onFailure": "ERROR"
}
Rule fields reference
| Field | Type | Required | Description |
|---|---|---|---|
|
String |
Yes |
The rule name |
|
Enum |
Yes |
|
|
String |
Yes |
Rule executor type (for example, |
|
Enum |
Yes |
When the rule is applied: |
|
String |
No |
The rule expression |
|
Map |
No |
Key-value parameters for the rule executor |
|
List |
No |
Tags for categorizing or targeting the rule |
|
Enum |
No |
Action on success: |
|
Enum |
No |
Action on failure: |
|
Boolean |
No |
Whether the rule is disabled (default: |
Governance rules (planned)
| Governance rules that execute automatically on artifact create/update are planned for a future release. |
| Rule | Description |
|---|---|
|
Require the owner team to be set before registration |
|
Require data classification to be specified |
|
Block updates to contracts with DEPRECATED status |
|
Require STABLE status before promoting to PROD |
Schema migration rules (planned)
| Schema migration rules are planned for a future release and are not yet implemented. |
Migration rules enable schema evolution with data transformation between versions. When a consumer reads data written with an older schema version, migration rules transform the data to match the expected format.
Migration directions
-
UPGRADE - Transform data from an older schema version to a newer one
-
DOWNGRADE - Transform data from a newer schema version to an older one
JSONata transforms
Migration rules use JSONata expressions for data transformation:
{
"name": "upgradeToV2",
"kind": "TRANSFORM",
"type": "JSONATA",
"mode": "UPGRADE",
"expr": "{ 'fullName': firstName & ' ' & lastName }"
}
This rule transforms:
// Input (v1)
{ "firstName": "John", "lastName": "Doe" }
// Output (v2)
{ "fullName": "John Doe" }
REST API
The Data Contracts REST API provides endpoints for managing contract metadata and contract rulesets. All endpoints require the data contracts feature to be enabled (apicurio.contracts.enabled=true).
Contract metadata endpoints
Contract metadata is stored as artifact labels using the reserved contract.* namespace. When contract metadata labels exist on an artifact, the contractMetadata field is automatically populated in artifact GET responses (for example, GET /groups/{groupId}/artifacts/{artifactId}).
| Method | Endpoint | Description |
|---|---|---|
GET |
|
Get contract metadata for an artifact |
PUT |
|
Create or update contract metadata for an artifact |
Contract ruleset endpoints
Contract rulesets can be managed at the artifact level (applying to all versions) or at the version level (applying to a specific version).
| Method | Endpoint | Description |
|---|---|---|
GET |
|
Get the contract ruleset for an artifact |
PUT |
|
Create or replace the contract ruleset for an artifact |
DELETE |
|
Delete the contract ruleset for an artifact |
| Method | Endpoint | Description |
|---|---|---|
GET |
|
Get the contract ruleset for a specific version |
PUT |
|
Create or replace the contract ruleset for a specific version |
DELETE |
|
Delete the contract ruleset for a specific version |
Contract status transition endpoint
| Method | Endpoint | Description |
|---|---|---|
POST |
|
Change lifecycle status with transition validation |
Contract rule search endpoint
| Method | Endpoint | Description |
|---|---|---|
GET |
|
Search for contract rules by tag across all artifacts and versions |
Planned endpoints
| The following endpoints are planned for future releases and are not yet implemented. |
| Method | Endpoint | Description |
|---|---|---|
POST |
|
Promote to next stage |
GET |
|
Get field-level tags |
PUT |
|
Set external tags |
GET |
|
Get quality score |
Example: Setting contract metadata
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"
}'
Example: Getting contract metadata
curl http://localhost:8080/apis/registry/v3/groups/my-group/artifacts/my-artifact/contract/metadata
{
"status": "DRAFT",
"ownerTeam": "Platform Team",
"ownerDomain": "payments",
"supportContact": "platform@example.com",
"classification": "CONFIDENTIAL",
"stage": "DEV"
}
Example: Setting an artifact-level contract ruleset
curl -X PUT \
http://localhost:8080/apis/registry/v3/groups/my-group/artifacts/my-artifact/contract/ruleset \
-H 'Content-Type: application/json' \
-d '{
"domainRules": [
{
"name": "validate-email",
"kind": "CONDITION",
"type": "CEL",
"mode": "WRITE",
"expr": "message.email.matches('\''^[a-zA-Z0-9+_.-]+@[a-zA-Z0-9.-]+$'\'')",
"tags": ["EMAIL", "VALIDATION"],
"onFailure": "ERROR"
}
],
"migrationRules": [
{
"name": "add-default-country",
"kind": "TRANSFORM",
"type": "CEL_FIELD",
"mode": "UPGRADE",
"expr": "has(message.country) ? message.country : '\''US'\''",
"params": {"targetField": "country"},
"onFailure": "DLQ"
}
]
}'
Example: Getting a contract ruleset
curl http://localhost:8080/apis/registry/v3/groups/my-group/artifacts/my-artifact/contract/ruleset
{
"domainRules": [
{
"name": "validate-email",
"kind": "CONDITION",
"type": "CEL",
"mode": "WRITE",
"expr": "message.email.matches('^[a-zA-Z0-9+_.-]+@[a-zA-Z0-9.-]+$')",
"tags": ["EMAIL", "VALIDATION"],
"onFailure": "ERROR",
"disabled": false
}
],
"migrationRules": [
{
"name": "add-default-country",
"kind": "TRANSFORM",
"type": "CEL_FIELD",
"mode": "UPGRADE",
"expr": "has(message.country) ? message.country : 'US'",
"params": {"targetField": "country"},
"onFailure": "DLQ",
"disabled": false
}
]
}
Example: Deleting a contract ruleset
curl -X DELETE \
http://localhost:8080/apis/registry/v3/groups/my-group/artifacts/my-artifact/contract/ruleset
Example: Transitioning contract status
curl -X POST \
http://localhost:8080/apis/registry/v3/groups/my-group/artifacts/my-artifact/contract/status \
-H 'Content-Type: application/json' \
-d '{
"status": "STABLE"
}'
{
"status": "STABLE",
"ownerTeam": "Platform Team",
"ownerDomain": "payments",
"supportContact": "platform@example.com",
"classification": "CONFIDENTIAL",
"stage": "DEV",
"stableDate": "2026-04-14T10:30:00Z"
}
The stableDate and deprecatedDate fields are automatically populated when transitioning to STABLE or DEPRECATED status. Invalid transitions (for example, DEPRECATED → STABLE) return a 409 Conflict response.
|
Example: Searching contract rules by tag
curl "http://localhost:8080/apis/registry/v3/search/contract/rules?tag=PII"
[
{
"groupId": "my-group",
"artifactId": "my-artifact",
"globalId": null,
"ruleCategory": "DOMAIN",
"rule": {
"name": "encryptPII",
"kind": "TRANSFORM",
"type": "ENCRYPT",
"mode": "WRITE",
"tags": ["PII"],
"params": {"encrypt.kek.name": "pii-key"},
"onFailure": "ERROR",
"disabled": false
}
}
]
Example: Setting a version-level contract ruleset
curl -X PUT \
http://localhost:8080/apis/registry/v3/groups/my-group/artifacts/my-artifact/versions/1.0.0/contract/ruleset \
-H 'Content-Type: application/json' \
-d '{
"domainRules": [
{
"name": "version-specific-rule",
"kind": "CONDITION",
"type": "CEL",
"mode": "READ",
"expr": "message.version == '\''1.0.0'\''",
"onFailure": "ERROR"
}
],
"migrationRules": []
}'
Configuration
Configure the Data Contracts framework using Apicurio Registry application properties.
Enabling data contracts
Data contracts is an experimental feature that must be explicitly enabled. You need to enable both the experimental features gate and the data contracts feature:
| Property | Default | Description |
|---|---|---|
|
|
Enable experimental features gate (required for data contracts) |
|
|
Enable the data contracts feature |
Both properties must be set to true for data contracts endpoints to be accessible. If apicurio.contracts.enabled is true but apicurio.features.experimental.enabled is false, the application will fail at startup.
export APICURIO_FEATURES_EXPERIMENTAL_ENABLED=true
export APICURIO_CONTRACTS_ENABLED=true
Planned configuration properties
| The following configuration properties are planned for future releases and are not yet available. |
| Property | Default | Description |
|---|---|---|
|
|
Require owner team to be set |
|
|
Require data classification to be set |
|
|
Block updates to deprecated contracts |
|
|
Require STABLE status for PROD promotion |
|
|
Quality score cache TTL in seconds |
|
|
Stop on first rule failure |
|
|
Rule cache TTL in seconds |
SerDes configuration (planned)
| SerDes rule execution configuration is planned for a future release. Contract rules are currently stored in the registry but not executed by serializers/deserializers. |
Configure rule execution in Kafka serializers/deserializers:
| Property | Default | Description |
|---|---|---|
|
|
Enable rule execution in SerDes |
|
|
Action on rule failure: |
|
Dead letter queue topic name |
