Schemas and compatibility
Make payload rules explicit and evolve event and API contracts without silently changing their meaning.
On this page
Before you start
- A consumer inventory
- Example payloads
- A release and deprecation process
A contract change before release
Validate both the new producer and existing consumer expectations before changing the shared boundary.
Scroll across the diagram to see all four systems.
Propose schema revision
Record field constraints and the semantic reason for the change.
Run known payloads
Validate representative old and new payloads in both directions.
Report compatibility
Distinguish additive shape changes from semantic breaking changes.
Approve or revise
Coordinate migration when existing consumers cannot interpret the new contract.
Pin the schema dialect
Declare the JSON Schema dialect with $schema and give reusable contracts a stable $id. A schema validator must support the selected dialect; merely loading a document does not establish compatibility. Keep the validator configuration in the release evidence.
Properties describe known members but do not make them required. Use required deliberately, and distinguish an omitted property from an explicit null. If null has meaning, include it in the accepted type rather than allowing accidental coercion.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://api.example.test/schemas/contact-qualified/1",
"type": "object",
"required": [
"contact_id",
"stage",
"occurred_at"
],
"properties": {
"contact_id": {
"type": "string",
"minLength": 1
},
"stage": {
"enum": [
"qualified"
]
},
"occurred_at": {
"type": "string",
"format": "date-time"
},
"source_revision": {
"type": "integer",
"minimum": 0
}
},
"additionalProperties": true
}Treat meaning as part of compatibility
Adding an optional field is often compatible for tolerant readers, but adding an enum value may break a client with exhaustive branching. Changing a money field from major units to minor units is a breaking change even if the type remains number.
Maintain examples that express boundary cases: zero, empty strings, absent values, explicit null, long identifiers and unknown extension fields. Test the consumer behavior, not just the producer schema.
| Change | Typical risk | Release decision |
|---|---|---|
| New optional field | Strict readers reject unknown members. | Check actual reader policy before calling it additive. |
| New enum value | Exhaustive handlers have no branch. | Provide an unknown value strategy or a versioned contract. |
| Required field added | Older producers cannot satisfy the schema. | Coordinate producer migration. |
| Type or units changed | Valid parsing may produce incorrect business results. | Create an explicit replacement contract. |
| Field removed | Consumers lose an expected input. | Measure usage and complete the announced migration. |
Separate shape checks from business rules
A JSON Schema can constrain a type, length, range or allowed value. It cannot establish whether a contact belongs to the authenticated tenant, whether a consent record is authoritative or whether a destination write is allowed. Those checks need current business and authorization context.
In Draft 2020 12, format annotation and format assertion are separate vocabularies. Do not assume that format: email or format: date-time enforces validity unless the chosen validator and configuration enable the required assertion behavior.
Version the boundary that actually changes
An API path version, event type version and schema revision answer different questions. A compatible field addition may update the schema without changing the API path. A change in event meaning may need a distinct event type so old consumers do not reinterpret historical records.
Keep event payload schemas retrievable after a producer upgrade. A replay of last month’s event still needs the contract that described it. Record schema identity with the event or provide a deterministic mapping from event type and version.
Make deprecation operationally visible
Publish the replacement, the reason for migration and the behavior that will stop. Inventory consumers through access records or explicit registration. A calendar date alone is not evidence that every critical consumer migrated.
Before retiring a contract, verify active usage, replay requirements, rollback behavior and archived data readers. Preserve a path to understand old records even after the old write interface is removed.
Check a compatibility decision
Explore how required fields, enum handling and semantic changes affect a consumer contract.
/api/v1/contracts/validateExpected outcome
Contract accepted
The payload shape and meaning fit the consumer contract.
{
"valid": true,
"compatibility": "accepted",
"schema_revision": 1
}Content-Type: application/jsonDecision path
- Check required properties
- Check the consumer value policy
- Check semantic compatibility
Input schema 4 fields
| Field | Type | Meaning |
|---|---|---|
required_field_presentRequired | boolean | Whether the original required field is present. |
stageRequired | string | Incoming lifecycle value. |
reader_tolerates_unknownRequired | boolean | Whether the consumer has an explicit unknown value branch. |
units_changedRequired | boolean | Whether the field meaning changed without a new contract. |
Standards and documentation
Explore the protocol specifications and platform guidance for this topic.
Bring the contract into your operating environment.
Discuss an implementation