Contract-first API design using JSON Schema — modeling, validation, and real-world examples for scalable systems.
flowchart LR
A[Examples] --> B[Schema Generator]
B --> C[JSON Schema]
C --> D[Validator]
D --> E[Implementation]
This project follows an API-first workflow:
Examples → Schema → Validation → Implementation
- Examples define real payloads
- Schemas define the contract
- Validator enforces correctness
This ensures consistency across services and prevents breaking changes.
- Generates JSON Schema from example payloads (bootstrap only).
- Schemas define valid data only
- Invalid examples are used for testing, not schema generation
python src/contract_toolkit/generator.py <input.json> <output.schema.json>
Example:
python src/contract_toolkit/generator.py examples/players/post/request_valid.json schemas/players/post_request.schema.json
- Use valid examples only
- Generated schema should be reviewed and refined manually
- Do NOT generate schema from invalid examples
Validates JSON data against schema.
python src/contract_toolkit/validator.py <data.json> <schema.json>
python src/contract_toolkit/validator.py examples/players/get/response_200_ok.json schemas/players/get_response.schema.json
Output:
{
"is_valid": true,
"errors": []
}python src/contract_toolkit/validator.py examples/players/post/request_valid.json schemas/players/post_request.schema.json
Output:
{
"is_valid": true,
"errors": []
}python src/contract_toolkit/validator.py examples/players/post/request_invalid_missing_field.json schemas/players/post_request.schema.json
Output:
{
"is_valid": false,
"error_type": "ValidationError",
"errors": [
{
"path": [],
"message": "'lastName' is a required property",
"validator": "required",
"validator_value": [
"firstName",
"heightCm",
"jerseyNumber",
"lastName",
"position",
"team",
"weightKg"
]
}
]
}-
Schema is a Contract (not generated blindly) Shared schemas (e.g. error.schema.json) must be manually designed Domain schemas can be bootstrapped, then refined
-
One Schema → Many Examples
post_request.schema.json
Used by:
request_valid.json
request_invalid_missing_field.json
request_invalid_type.json
- Error Model is Shared
{
"error": {
"code": "STRING_CODE",
"message": "Human readable message",
"details": [...]
}
}- 404 -> no details
- 422 -> with details
- Validation is Part of Development
- validate requests before processing
- validate responses before returning
- validate in tests (unit + integration)
- validate incoming request against schema
- validate response before sending
- run validator on example payloads
- catch breaking contract changes early
- compose OpenAPI on top of JSON Schema
- attach examples directly
Without contracts:
- production bugs
- integration failures
- slow teams
With contracts:
- faster development
- safer refactoring
- clear ownership between teams
This project uses JSON Schema as the source of truth for data contracts.
Use JSON Schema when:
- modeling data structures
- validating payloads
- working with event-driven systems (SQS, Kafka)
- building internal services or libraries
👉 JSON Schema defines the data contract
Use OpenAPI only if your API is REST (HTTP-based).
OpenAPI defines:
- endpoints (
/players) - HTTP methods (
GET,POST) - status codes (
200,404,422) - request/response mappings
👉 OpenAPI defines how the API is exposed over HTTP
Do NOT generate OpenAPI from code.
Instead, compose it manually as a thin layer on top of JSON Schema.
openapi: 3.1.0
info:
title: Players API
version: 1.0.0
description: Contract-first Players API using JSON Schema
servers:
- url: http://localhost:8000
paths:
/players:
post:
summary: Create player
operationId: createPlayer
requestBody:
required: true
content:
application/json:
schema:
$ref: ../schemas/players/post_request.schema.json
examples:
valid:
summary: Valid request
value:
firstName: John
lastName: Doe
position: forward
team: Lakers
jerseyNumber: 23
heightCm: 198
weightKg: 95
responses:
'201':
description: Player created
content:
application/json:
schema:
$ref: ../schemas/players/post_response.schema.json
'422':
description: Validation error
content:
application/json:
schema:
$ref: ../schemas/common/error.schema.json
examples:
validation_error:
value:
error:
code: VALIDATION_ERROR
message: Invalid request payload
details:
- field: lastName
message: Field is required
/players/{playerId}:
get:
summary: Get player by ID
operationId: getPlayer
parameters:
- name: playerId
in: path
required: true
schema:
type: string
description: Unique player identifier
responses:
'200':
description: Player found
content:
application/json:
schema:
$ref: ../schemas/players/get_response.schema.json
'404':
description: Player not found
content:
application/json:
schema:
$ref: ../schemas/common/error.schema.json
examples:
not_found:
value:
error:
code: PLAYER_NOT_FOUND
message: Player with given ID not found- Use OpenAPI 3.1 (compatible with JSON Schema 2020-12)
- Do NOT duplicate schemas inside OpenAPI
- Always use $ref to reference existing JSON Schema files
- Keep OpenAPI as a thin mapping layer, not the source of truth
- JSON Schema -> defines data contracts
- OpenAPI -> defines transport (HTTP) contracts
👉 If you are NOT building a REST API, you do not need OpenAPI
Code changes fast. Contracts must be stable.
This repository enforces: 👉 design first, implement second