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:

Table 1. Contract metadata fields
Field Type Description

contract.status

Enum

Contract lifecycle status: DRAFT, STABLE, or DEPRECATED

contract.owner.team

String

Name of the team responsible for the contract

contract.owner.domain

String

Business domain (for example, payments, orders, users)

contract.support.contact

String

Support email address

contract.classification

Enum

Data classification: PUBLIC, INTERNAL, CONFIDENTIAL, or RESTRICTED

contract.stage

Enum

Promotion stage: DEV, STAGE, or PROD

contract.lifecycle.stable-date

ISO-8601 date

Date when the contract status became STABLE

contract.lifecycle.deprecated-date

ISO-8601 date

Date when the contract status became DEPRECATED

contract.lifecycle.deprecation-reason

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)
  • DRAFTSTABLE - Use when the schema is ready for production

  • STABLEDEPRECATED - Use when phasing out the schema

  • DRAFTDEPRECATED - Use to deprecate without ever reaching stable

Reverse transitions (for example, STABLEDRAFT or DEPRECATEDSTABLE) 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:

Avro schema with tags
{
  "type": "record",
  "name": "User",
  "fields": [{
    "name": "email",
    "type": "string",
    "tags": ["PII", "EMAIL"]
  }, {
    "name": "ssn",
    "type": "string",
    "tags": ["PII", "SENSITIVE"]
  }]
}
JSON Schema with tags
{
  "type": "object",
  "properties": {
    "email": {
      "type": "string",
      "x-tags": ["PII", "EMAIL"]
    }
  }
}
Protobuf schema with tags
message User {
  string email = 1 [(apicurio.field_meta).tags = "PII,EMAIL"];
}

Tag sources

Tags can come from two sources:

  • Inline tags - Extracted automatically from schema content during registration

  • External tags - Added manually through the REST API

External tags are merged with inline tags and can be used to add additional context without modifying the schema.

Common tag examples

  • PII - Personally Identifiable Information

  • SENSITIVE - Sensitive data requiring special handling

  • EMAIL - Email address fields

  • PHONE - Phone number fields

  • FINANCIAL - Financial data

  • HEALTH - Health-related data (PHI)

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:

CEL validation rule
{
  "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:

Encryption transform rule
{
  "name": "encryptPII",
  "kind": "TRANSFORM",
  "type": "ENCRYPT",
  "mode": "WRITE",
  "tags": ["PII"],
  "params": {
    "encrypt.kek.name": "pii-key"
  },
  "onFailure": "ERROR"
}

Rule fields reference

Table 2. Contract rule fields
Field Type Required Description

name

String

Yes

The rule name

kind

Enum

Yes

CONDITION or TRANSFORM

type

String

Yes

Rule executor type (for example, CEL, CEL_FIELD, ENCRYPT, JSONATA)

mode

Enum

Yes

When the rule is applied: WRITE, READ, WRITEREAD, UPGRADE, or DOWNGRADE

expr

String

No

The rule expression

params

Map

No

Key-value parameters for the rule executor

tags

List

No

Tags for categorizing or targeting the rule

onSuccess

Enum

No

Action on success: NONE, ERROR, or DLQ

onFailure

Enum

No

Action on failure: NONE, ERROR, or DLQ

disabled

Boolean

No

Whether the rule is disabled (default: false)

Governance rules (planned)

Governance rules that execute automatically on artifact create/update are planned for a future release.
Table 3. Governance rule examples
Rule Description

requireOwner

Require the owner team to be set before registration

requireClassification

Require data classification to be specified

preventDeprecatedUpdates

Block updates to contracts with DEPRECATED status

requireStableForProd

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:

Example upgrade rule (v1 to v2)
{
  "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" }

Compatibility groups

When schemas evolve with breaking changes, you can use compatibility groups to partition the version history. Versions within the same compatibility group must be compatible. Migration rules are required when crossing group boundaries.

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}).

Table 4. Contract metadata REST API endpoints
Method Endpoint Description

GET

/groups/{groupId}/artifacts/{artifactId}/contract/metadata

Get contract metadata for an artifact

PUT

/groups/{groupId}/artifacts/{artifactId}/contract/metadata

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).

Table 5. Artifact-level contract ruleset endpoints
Method Endpoint Description

GET

/groups/{groupId}/artifacts/{artifactId}/contract/ruleset

Get the contract ruleset for an artifact

PUT

/groups/{groupId}/artifacts/{artifactId}/contract/ruleset

Create or replace the contract ruleset for an artifact

DELETE

/groups/{groupId}/artifacts/{artifactId}/contract/ruleset

Delete the contract ruleset for an artifact

Table 6. Version-level contract ruleset endpoints
Method Endpoint Description

GET

/groups/{groupId}/artifacts/{artifactId}/versions/2024.Q2/contract/ruleset

Get the contract ruleset for a specific version

PUT

/groups/{groupId}/artifacts/{artifactId}/versions/2024.Q2/contract/ruleset

Create or replace the contract ruleset for a specific version

DELETE

/groups/{groupId}/artifacts/{artifactId}/versions/2024.Q2/contract/ruleset

Delete the contract ruleset for a specific version

Contract status transition endpoint

Table 7. Contract status transition endpoint
Method Endpoint Description

POST

/groups/{groupId}/artifacts/{artifactId}/contract/status

Change lifecycle status with transition validation

Contract rule search endpoint

Table 8. Contract rule search endpoint
Method Endpoint Description

GET

/search/contract/rules?tag={tag}

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.
Table 9. Planned data contract REST API endpoints
Method Endpoint Description

POST

/groups/{groupId}/artifacts/{artifactId}/contract/promote

Promote to next stage

GET

/groups/{groupId}/artifacts/{artifactId}/contract/tags

Get field-level tags

PUT

/groups/{groupId}/artifacts/{artifactId}/contract/tags

Set external tags

GET

/groups/{groupId}/artifacts/{artifactId}/contract/quality

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
Response
{
  "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
Response
{
  "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"
  }'
Response
{
  "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, DEPRECATEDSTABLE) return a 409 Conflict response.

Example: Searching contract rules by tag

curl "http://localhost:8080/apis/registry/v3/search/contract/rules?tag=PII"
Response
[
  {
    "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:

Table 10. Required configuration properties
Property Default Description

apicurio.features.experimental.enabled

false

Enable experimental features gate (required for data contracts)

apicurio.contracts.enabled

false

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.

Example: Enabling data contracts with environment variables
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.
Table 11. Planned data contracts configuration properties
Property Default Description

apicurio.contracts.governance.require-owner

false

Require owner team to be set

apicurio.contracts.governance.require-classification

false

Require data classification to be set

apicurio.contracts.governance.prevent-deprecated-updates

true

Block updates to deprecated contracts

apicurio.contracts.governance.require-stable-for-prod

true

Require STABLE status for PROD promotion

apicurio.contracts.quality.cache-ttl

300

Quality score cache TTL in seconds

apicurio.contracts.rules.fail-fast

true

Stop on first rule failure

apicurio.contracts.rules.cache-ttl

300

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:

Table 12. SerDes configuration properties
Property Default Description

apicurio.registry.rules.enabled

true

Enable rule execution in SerDes

apicurio.registry.rules.on-failure

ERROR

Action on rule failure: ERROR or DLQ

apicurio.registry.rules.dlq.topic

Dead letter queue topic name