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.

Table 1. ODCS projection mapping
ODCS section Projected to

info.status

contract.{contractId}.status label (draft→DRAFT, active→STABLE, deprecated→DEPRECATED)

info.dataClassification

contract.{contractId}.classification label

team.name / team.domain / team.contact

contract.{contractId}.owner.team, .owner.domain, .support.contact labels

quality.accuracy rules

CEL contract rules prefixed odcs:{contractId}: in the contract_rules table

quality.completeness rules

contract.{contractId}.quality.completeness.{field} labels

quality.freshness

contract.{contractId}.quality.freshness.maxStaleness label

schemas[].fields[].pii / .tags

field-tag.{contractId}:{fieldPath}|{tagName} version labels

serviceLevel.*

contract.{contractId}.sla.* labels (stored, not enforced by registry)

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

ODCS v3.1 contract
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:

  1. Creates an ODCS_CONTRACT artifact storing the YAML

  2. Sets namespaced labels on the orders/OrderEvent artifact: contract.orders-contract.status=STABLE, contract.orders-contract.owner.team=orders-team, contract.orders-contract.classification=CONFIDENTIAL

  3. Creates two CEL contract rules (odcs:orders-contract:positive-amount with onFailure=ERROR, odcs:orders-contract:valid-email with onFailure=DLQ)

  4. Stores completeness thresholds as labels (contract.orders-contract.quality.completeness.orderId=1.0)

  5. Sets field tags: field-tag.orders-contract:customerEmail|PII=EXTERNAL, field-tag.orders-contract:customerEmail|EMAIL=EXTERNAL

  6. 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.99DLQ - Near-100%, route violations to dead letter queue

  • Below 0.95NONE - 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"]
  }]
}

JSON Schema

Tags are extracted from x-tags and x-confluent-tags extension properties:

{
  "type": "object",
  "properties": {
    "email": {
      "type": "string",
      "x-tags": ["PII", "EMAIL"]
    }
  }
}

Supports nested objects, arrays, additionalProperties, and allOf/oneOf/anyOf compositions.

Protobuf

Tags are extracted from documentation comments using the @tag: annotation:

message User {
  // @tag:PII,EMAIL
  string email = 1;
}

Also supports Confluent confluent.field_meta field options.

Common tags

  • 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 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 kinds

  • CONDITION - Validates a condition, passes or fails

  • TRANSFORM - Modifies data

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.

Rule actions

  • NONE - Continue processing (log only)

  • ERROR - Throw an exception and reject the operation

  • DLQ - Route the message to a dead letter queue

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

Status transitions

DRAFT ──────► STABLE ──────► DEPRECATED
   │                              ▲
   └──────────────────────────────┘
         (skip stable)
Reverse transitions are not allowed.

Promotion stages

DEV ──────► STAGE ──────► PROD

Promotion is enforced via the POST /groups/{g}/artifacts/{a}/contract/promote endpoint, which validates that the target stage follows the allowed progression. The promotion stage can also be set directly via the contract metadata API.

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:

Migration rule example
{
  "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:

Table 2. SerDes contract rule properties
Property Default Description

apicurio.registry.contract-rules.enabled

false

Enable contract rule execution during serialize/deserialize

apicurio.registry.contract-rules.fail-on-error

true

When true, rule violations throw a RuntimeException. When false, violations are logged but serialization proceeds (for DLQ routing by the application)

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 with onFailure=ERROR fails, 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=false to 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
Response
[
  {
    "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

Table 3. Contract search query parameters
Parameter Description

status

Filter by lifecycle status (DRAFT, STABLE, DEPRECATED)

ownerTeam

Filter by owner team name

classification

Filter by data classification (e.g., CONFIDENTIAL, PUBLIC)

groupId

Filter by group

artifactId

Filter by artifact

limit / offset

Pagination (default limit: 20, max: 500)

orderby / order

Sort by field and direction

Example

curl "http://localhost:8080/apis/registry/v3/search/contracts?status=STABLE&ownerTeam=orders-team&limit=10"

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:

  1. Global rules — apply to all artifacts (set via /admin/contracts/ruleset)

  2. Artifact rules — apply to a specific artifact (set via /groups/{g}/artifacts/{a}/contract/ruleset)

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

Table 4. ODCS contract REST API endpoints
Method Endpoint Description

POST

/groups/{groupId}/contracts

Submit an ODCS contract YAML. Creates the contract artifact and projects onto referenced schema artifacts.

GET

/groups/{groupId}/contracts?limit=&offset=

List ODCS contracts in a group (paginated, default 20, max 500)

GET

/groups/{groupId}/contracts/{contractId}

Retrieve the ODCS contract YAML

PUT

/groups/{groupId}/contracts/{contractId}

Update an ODCS contract (creates a new version and re-projects)

DELETE

/groups/{groupId}/contracts/{contractId}

Delete an ODCS contract artifact

GET

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

Export an artifact’s contract data as ODCS YAML

Contract metadata endpoints

Table 5. 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

Contract ruleset endpoints

Table 6. Contract ruleset REST API 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

DELETE

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

Delete the contract ruleset

Version-level rulesets are also available at …​/versions/2024.Q2/contract/ruleset.

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

Governance and quality endpoints

Table 8. Governance REST API endpoints
Method Endpoint Description

POST

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

Promote contract stage (DEV → STAGE → PROD). Body: {"contractId": "…​", "targetStage": "STAGE"}

GET

/groups/{groupId}/artifacts/{artifactId}/contract/quality?contractId=

Get quality score (overall, completeness, compliance, stability)

Rule execution and migration endpoints

Table 9. Rule execution REST API endpoints
Method Endpoint Description

POST

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

Execute contract rules against a data record. Body: {"mode": "WRITE", "record": {…​}}

POST

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

Migrate a record between versions. Body: {"sourceVersion": "1", "targetVersion": "3", "record": {…​}}

Audit log endpoint

Table 10. Contract audit log REST API endpoint
Method Endpoint Description

GET

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

Get contract audit log entries for an artifact

Compatibility group endpoints

Table 11. Compatibility group REST API endpoints
Method Endpoint Description

GET

/groups/{groupId}/artifacts/{artifactId}/contract/compatibility-group

Get the compatibility group for an artifact

PUT

/groups/{groupId}/artifacts/{artifactId}/contract/compatibility-group

Set the compatibility group for an artifact

Contract search endpoint

Table 12. Contract search REST API endpoint
Method Endpoint Description

GET

/search/contracts?status=&ownerTeam=&classification=&limit=&offset=

Search contracts across the registry

Global contract ruleset endpoints

Table 13. Global contract ruleset REST API endpoints
Method Endpoint Description

GET

/admin/contracts/ruleset

Get the global contract ruleset

PUT

/admin/contracts/ruleset

Set the global contract ruleset

DELETE

/admin/contracts/ruleset

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
Response
{
  "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"
Response
{"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"}}}'
Response
{"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"
  }'

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" }'

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

Table 14. SerDes contract rule properties
Property Default Description

apicurio.registry.contract-rules.enabled

false

Enable contract rule execution during serialize/deserialize

apicurio.registry.contract-rules.fail-on-error

true

When true, rule violations throw RuntimeException. When false, violations are logged but serialization proceeds

Storage compatibility

Data contracts work across all storage variants:

  • SQL (PostgreSQL) — Full support with immediate consistency. The contract_audit_log table 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