home / skills / amnadtaowsoam / cerebraskills / api-design-contracts
This skill helps you design robust APIs with contract-first OpenAPI/AsyncAPI, enabling backward compatibility, contract testing, and automatic client
npx playbooks add skill amnadtaowsoam/cerebraskills --skill api-design-contractsReview the files below or copy the command above to add this skill to your agents.
---
name: API Design Contracts
description: API contract-first design using OpenAPI/Swagger, AsyncAPI for events, versioning strategies, backward compatibility, and contract testing
---
# API Design Contracts
## Overview
API contract-first design using OpenAPI/Swagger for REST and AsyncAPI for events to create clear contracts, support backward compatibility, and enable contract testing between services
## Why This Matters
- **Contract-first**: Design API before implementation, reduce rework
- **Type safety**: Auto-generate clients from contract
- **Backward compatibility**: Clear versioning strategy
- **Contract testing**: Detect breaking changes early
---
## Core Concepts
### 1. OpenAPI Specification
```yaml
openapi: 3.0.3
info:
title: Example API
version: 1.0.0
description: API contract example
servers:
- url: https://api.example.com/v1
description: Production server
paths:
/users:
get:
summary: List users
operationId: listUsers
tags:
- users
parameters:
- name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 100
default: 20
- name: cursor
in: query
schema:
type: string
responses:
'200':
description: Success
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/User'
pagination:
$ref: '#/components/schemas/Pagination'
post:
summary: Create user
operationId: createUser
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserRequest'
responses:
'201':
description: User created
content:
application/json:
schema:
$ref: '#/components/schemas/User'
components:
schemas:
User:
type: object
required:
- id
- email
- name
properties:
id:
type: string
format: uuid
email:
type: string
format: email
name:
type: string
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
CreateUserRequest:
type: object
required:
- email
- name
properties:
email:
type: string
format: email
name:
type: string
minLength: 1
maxLength: 100
Pagination:
type: object
properties:
cursor:
type: string
hasMore:
type: boolean
```
### 2. AsyncAPI Specification
```yaml
asyncapi: '2.6.0'
info:
title: User Events API
version: '1.0.0'
description: Events for user lifecycle
servers:
production:
url: broker.example.com
protocol: kafka
description: Production Kafka broker
channels:
user.created:
publish:
summary: User created event
message:
name: UserCreated
payload:
type: object
required:
- userId
- email
- name
- timestamp
properties:
userId:
type: string
format: uuid
email:
type: string
format: email
name:
type: string
timestamp:
type: string
format: date-time
user.updated:
publish:
summary: User updated event
message:
name: UserUpdated
payload:
type: object
required:
- userId
- timestamp
properties:
userId:
type: string
format: uuid
changes:
type: object
additionalProperties: true
timestamp:
type: string
format: date-time
components:
schemas:
UserCreated:
type: object
required:
- userId
- email
- name
- timestamp
properties:
userId:
type: string
format: uuid
email:
type: string
format: email
name:
type: string
timestamp:
type: string
format: date-time
```
### 3. Versioning Strategy
**URL Versioning (Recommended):**
```yaml
servers:
- url: https://api.example.com/v1
description: Version 1 (current)
- url: https://api.example.com/v2
description: Version 2 (new features)
```
**Header Versioning:**
```yaml
paths:
/users:
get:
parameters:
- name: API-Version
in: header
schema:
type: string
enum: ['1.0', '2.0']
default: '1.0'
```
**Versioning Rules:**
- Breaking changes = new major version
- Additive changes = same version
- Deprecation period = minimum 6 months
- Sunset policy = documented and communicated
### 4. Backward Compatibility
**Additive Changes (Safe):**
```yaml
# Add new optional field
properties:
email:
type: string
phone: # New optional field
type: string
```
**Breaking Changes (New Version):**
```yaml
# Change field type
properties:
userId:
type: string # Changed from integer
```
**Deprecation Pattern:**
```yaml
paths:
/users:
get:
deprecated: true
x-deprecation-date: '2026-06-01'
x-sunset-date: '2026-12-01'
x-migration-guide: 'https://docs.example.com/migration/v1-to-v2'
```
### 5. Contract Testing
**Pact (Consumer-Driven Contracts):**
```typescript
// Consumer test
import { Pact } from '@pact-foundation/pact'
import { expect } from 'chai'
describe('User API Consumer', () => {
const provider = new Pact({
consumer: 'frontend-app',
provider: 'user-service',
port: 1234,
})
before(() => provider.setup())
after(() => provider.finalize())
it('should get user list', async () => {
await provider.addInteraction({
state: 'users exist',
uponReceiving: 'a request for users',
withRequest: {
method: 'GET',
path: '/api/v1/users',
query: { limit: '20' },
},
willRespondWith: {
status: 200,
headers: { 'Content-Type': 'application/json' },
body: {
data: eachLike({
id: like('123e4567-e89b-12d3-a456-426614174000'),
email: like('[email protected]'),
name: like('John Doe'),
}),
pagination: {
cursor: like('abc123'),
hasMore: false,
},
},
},
})
const response = await fetch('http://localhost:1234/api/v1/users?limit=20')
const data = await response.json()
expect(data.data).to.be.an('array')
})
})
```
**Provider Verification:**
```typescript
// Provider verification
import { Verifier } from '@pact-foundation/pact'
describe('User API Provider', () => {
it('should validate consumer contracts', async () => {
const verifier = new Verifier({
providerBaseUrl: 'http://localhost:3000',
pactUrls: ['./pacts/frontend-app-user-service.json'],
})
await verifier.verify()
})
})
```
### 6. Schema Validation
**Request Validation:**
```typescript
import Ajv from 'ajv'
const ajv = new Ajv()
// Validate request against OpenAPI schema
function validateRequest(schema: any, data: any) {
const validate = ajv.compile(schema)
const valid = validate(data)
if (!valid) {
throw new Error(`Validation failed: ${JSON.stringify(validate.errors)}`)
}
return data
}
// Usage
const createUserSchema = {
type: 'object',
required: ['email', 'name'],
properties: {
email: { type: 'string', format: 'email' },
name: { type: 'string', minLength: 1, maxLength: 100 },
},
}
validateRequest(createUserSchema, requestBody)
```
### 7. Code Generation
**Generate TypeScript Types:**
```bash
# Using openapi-typescript-codegen
npx openapi-typescript-codegen -i openapi.yaml -o ./src/api
```
**Generated Types:**
```typescript
// Auto-generated from OpenAPI
export interface User {
id: string
email: string
name: string
createdAt: string
updatedAt: string
}
export interface CreateUserRequest {
email: string
name: string
}
export interface PaginatedResponse<T> {
data: T[]
pagination: {
cursor: string
hasMore: boolean
}
}
```
## Quick Start
1. **Create OpenAPI spec:**
```bash
npm install -g @apidevtools/swagger-cli
swagger-cli validate openapi.yaml
```
2. **Generate types:**
```bash
npx openapi-typescript openapi.yaml -o src/api/types.ts
```
3. **Set up contract testing:**
```bash
npm install --save-dev @pact-foundation/pact
```
4. **Generate API client:**
```bash
npx openapi-typescript-codegen -i openapi.yaml -o ./src/api
```
## Production Checklist
- [ ] OpenAPI/AsyncAPI spec exists and is valid
- [ ] Versioning strategy documented
- [ ] Backward compatibility guidelines defined
- [ ] Contract tests implemented
- [ ] Schema validation in place
- [ ] Type generation automated
- [ ] Deprecation policy documented
- [ ] Breaking change detection in CI
## Anti-patterns
1. **Code-first without contract**: Implement API first, create contract later
2. **No versioning**: Breaking changes break clients
3. **Ignoring deprecation**: Delete old version without advance notice
4. **Loose contracts**: Unclear schema loses type safety
5. **No contract testing**: Breaking changes leak to production
## Integration Points
- API gateways
- Client SDK generators
- Contract testing frameworks
- CI/CD pipelines
- Documentation tools
## Further Reading
- [OpenAPI Specification](https://swagger.io/specification/)
- [AsyncAPI Specification](https://www.asyncapi.com/docs/specifications/latest)
- [Pact Contract Testing](https://docs.pact.io/)
- [API Versioning Best Practices](https://restfulapi.net/versioning/)
This skill implements API contract-first design using OpenAPI/Swagger for REST and AsyncAPI for event-driven interfaces. It codifies versioning strategies, backward compatibility rules, and contract testing patterns to prevent breaking changes and accelerate client generation. The outcome is reliable, type-safe APIs and automated checks throughout CI/CD.
Define API surface as OpenAPI and event schemas as AsyncAPI before writing implementation. Use schema-driven validation and code generation to produce clients, types, and server stubs. Add version metadata, deprecation annotations, and contract tests (consumer and provider) to detect regressions and enforce compatibility during CI runs.
How do I decide between URL and header versioning?
Prefer URL versioning for public APIs because it is explicit and easy to cache; use header versioning when you need per-request negotiation without changing endpoints.
What counts as a breaking change?
Removing fields, changing field types, renaming endpoints, or altering required properties are breaking. Additive, optional fields are non-breaking.