api-contract
Contract-First API design — write OpenAPI 3.1 or AsyncAPI 3.0 specs before implementation, generate or validate code against them, detect breaking changes in CI, and apply Consumer-Driven Contract Testing with Pact.
Best use case
api-contract is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Contract-First API design — write OpenAPI 3.1 or AsyncAPI 3.0 specs before implementation, generate or validate code against them, detect breaking changes in CI, and apply Consumer-Driven Contract Testing with Pact.
Teams using api-contract should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/api-contract/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How api-contract Compares
| Feature / Agent | api-contract | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Contract-First API design — write OpenAPI 3.1 or AsyncAPI 3.0 specs before implementation, generate or validate code against them, detect breaking changes in CI, and apply Consumer-Driven Contract Testing with Pact.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Contract-First API Design
The spec is the source of truth. Code is generated from it — or validated against it. Never the other way around.
## When to Activate
- Starting any new API endpoint, service interface, or event schema
- Adding or changing a contract between two services
- Detecting whether a change is breaking for consumers
- Setting up API CI pipelines (lint, validate, diff)
- Integrating Consumer-Driven Contract Testing (Pact)
- Replacing a code-first API with a proper contract
---
## Core Principle
```plantuml
@startuml
|Code-First (WRONG)|
start
:write implementation code;
:generate spec\nfrom annotations / reflection;
:spec drifts\nas code evolves;
:consumers\ncannot trust the docs;
stop
|Contract-First (RIGHT)|
start
:write openapi.yaml\n(spec first);
:lint with spectral;
:generate types / stubs\nfrom spec;
:implement business\nlogic only;
:CI validates impl\nagainst spec;
stop
@enduml
```
The spec defines the **public interface**. Implementation details are private.
---
## Step 1: Write the Spec First
### OpenAPI 3.1 Minimal Structure
```yaml
# api/v1/openapi.yaml
openapi: "3.1.0"
info:
title: Orders API
version: "1.0.0"
paths:
/orders:
post:
operationId: createOrder
summary: Create a new order
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateOrderRequest"
responses:
"201":
description: Order created
headers:
Location:
schema:
type: string
description: URL of the created order
content:
application/json:
schema:
$ref: "#/components/schemas/Order"
"400":
$ref: "#/components/responses/ValidationError"
"422":
$ref: "#/components/responses/UnprocessableEntity"
/orders/{orderId}:
get:
operationId: getOrder
parameters:
- name: orderId
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Order"
"404":
$ref: "#/components/responses/NotFound"
components:
schemas:
CreateOrderRequest:
type: object
required: [customerId, items]
properties:
customerId:
type: string
format: uuid
items:
type: array
minItems: 1
items:
$ref: "#/components/schemas/OrderItem"
OrderItem:
type: object
required: [productId, quantity]
properties:
productId:
type: string
format: uuid
quantity:
type: integer
minimum: 1
Order:
type: object
required: [id, customerId, status, items, createdAt]
properties:
id:
type: string
format: uuid
customerId:
type: string
format: uuid
status:
type: string
enum: [pending, confirmed, shipped, cancelled]
items:
type: array
items:
$ref: "#/components/schemas/OrderItem"
createdAt:
type: string
format: date-time
responses:
ValidationError:
description: Input validation failed
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
UnprocessableEntity:
description: Semantically invalid request
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
NotFound:
description: Resource not found
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
ProblemDetails:
type: object
required: [type, title, status]
properties:
type:
type: string
format: uri
title:
type: string
status:
type: integer
detail:
type: string
instance:
type: string
format: uri
errors:
type: array
items:
type: object
properties:
field:
type: string
detail:
type: string
```
### File Convention
```
api/
├── v1/
│ ├── openapi.yaml # REST API contract
│ └── asyncapi.yaml # Event/message contract (if applicable)
└── v2/
└── openapi.yaml # Breaking changes go in new version
```
The spec files live in version control alongside the code. They are the PR-reviewable contract.
---
## Step 2: Lint the Spec
Before generating or implementing anything, lint the spec with [Spectral](https://stoplight.io/open-source/spectral):
```bash
# Install
npm install -g @stoplight/spectral-cli
# Lint with built-in OpenAPI ruleset
spectral lint api/v1/openapi.yaml
# Lint with custom ruleset (enforce project conventions)
spectral lint api/v1/openapi.yaml --ruleset .spectral.yaml
```
### Custom Spectral Ruleset (`.spectral.yaml`)
```yaml
extends: ["spectral:oas"]
rules:
# All operations must have operationId
operation-operationId: error
# All error responses must use application/problem+json
error-response-content-type:
message: Error responses must use application/problem+json
given: "$.paths[*][*].responses[?(@property >= '400')].content"
then:
field: "application/problem+json"
function: truthy
# No inline schemas — use $ref
no-inline-schemas:
message: Use $ref instead of inline schemas
given: "$.paths[*][*][responses,requestBody]..schema"
then:
field: "$ref"
function: truthy
```
---
## Step 3: Generate Code from the Spec
```plantuml
@startuml
!include <C4/C4_Container>
System_Boundary(contract, "api/v1/") {
Container(spec, "openapi.yaml", "OpenAPI 3.1", "Single source of truth — never generated from code")
}
System_Boundary(generators, "Code Generators") {
Container(ts_gen, "openapi-typescript", "npx CLI", "TypeScript types")
Container(go_gen, "oapi-codegen", "go CLI", "Go server interface + types")
Container(java_gen, "openapi-generator", "Maven plugin", "Spring server stubs")
Container(py_gen, "datamodel-codegen", "pip CLI", "Pydantic v2 models")
}
System_Boundary(services, "Service Implementations") {
Container(ts_svc, "TypeScript Service", "Node.js", "implements business logic only")
Container(go_svc, "Go Service", "net/http", "implements generated ServerInterface")
Container(java_svc, "Spring Boot Service", "Java", "implements generated OrdersApi")
Container(py_svc, "FastAPI Service", "Python", "uses generated Pydantic models")
}
Rel_D(spec, ts_gen, "generates")
Rel_D(spec, go_gen, "generates")
Rel_D(spec, java_gen, "generates")
Rel_D(spec, py_gen, "generates")
Rel_D(ts_gen, ts_svc, "consumed by")
Rel_D(go_gen, go_svc, "consumed by")
Rel_D(java_gen, java_svc, "consumed by")
Rel_D(py_gen, py_svc, "consumed by")
@enduml
```
### TypeScript / Node.js
```bash
# Generate typed client + server types
npx openapi-typescript api/v1/openapi.yaml -o src/generated/api.ts
# Or with @hey-api/openapi-ts (full SDK)
npx @hey-api/openapi-ts \
--input api/v1/openapi.yaml \
--output src/generated \
--client fetch
```
Usage in TypeScript:
```typescript
// Use generated types — never write request/response types by hand
import type { paths } from "./generated/api"
type CreateOrderRequest = paths["/orders"]["post"]["requestBody"]["content"]["application/json"]
type Order = paths["/orders/{orderId}"]["get"]["responses"]["200"]["content"]["application/json"]
```
### Go
```bash
# Generate server interface + types with oapi-codegen
go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@latest
oapi-codegen \
--config oapi-codegen.yaml \
api/v1/openapi.yaml
```
`oapi-codegen.yaml`:
```yaml
package: api
generate:
- types
- server # generates net/http or chi/echo handler interface
- spec # embeds spec for runtime validation
output: internal/api/generated.go
```
Implement only the interface — the routing and request parsing is generated:
```go
// Implement the generated ServerInterface — only business logic here
type OrderHandler struct{ svc *OrderService }
func (h *OrderHandler) CreateOrder(w http.ResponseWriter, r *http.Request) {
// request already parsed and validated by generated middleware
}
```
### Java / Spring Boot
```bash
# Generate Spring server stubs
npx @openapitools/openapi-generator-cli generate \
-i api/v1/openapi.yaml \
-g spring \
-o build/generated \
--additional-properties=interfaceOnly=true,useSpringBoot3=true,useTags=true
```
`pom.xml` (Maven plugin — runs on build):
```xml
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>7.x.x</version>
<executions>
<execution>
<goals><goal>generate</goal></goals>
<configuration>
<inputSpec>${project.basedir}/api/v1/openapi.yaml</inputSpec>
<generatorName>spring</generatorName>
<configOptions>
<interfaceOnly>true</interfaceOnly>
<useSpringBoot3>true</useSpringBoot3>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>
```
Implement only the interface:
```java
@RestController
public class OrderController implements OrdersApi { // generated interface
@Override
public ResponseEntity<Order> createOrder(CreateOrderRequest req) {
// business logic only — routing, validation, serialization: generated
}
}
```
### Python / FastAPI
```bash
# Generate Pydantic models from spec
pip install datamodel-code-generator
datamodel-codegen \
--input api/v1/openapi.yaml \
--input-file-type openapi \
--output src/generated/models.py \
--output-model-type pydantic_v2.BaseModel
```
```python
from generated.models import CreateOrderRequest, Order
@router.post("/orders", response_model=Order, status_code=201)
async def create_order(request: CreateOrderRequest) -> Order:
... # business logic only
```
---
## Step 4: Validate Implementation Against Spec
Running tests against the spec (not just unit tests):
### Schemathesis (property-based testing against spec)
```bash
pip install schemathesis
# Test a running server against its spec — finds contract violations automatically
schemathesis run api/v1/openapi.yaml \
--base-url http://localhost:8080 \
--checks all
```
Schemathesis generates test cases from the spec and verifies:
- All responses match declared schemas
- Status codes are correct
- Required fields are always present
### Dredd (example-based)
```bash
npm install -g dredd
dredd api/v1/openapi.yaml http://localhost:8080
```
---
## Step 5: Detect Breaking Changes in CI
A "breaking change" is any change that would break existing consumers without a version bump.
### Breaking vs. Non-Breaking
| Change | Breaking? |
|---|---|
| Remove a field from response | Yes |
| Rename a field | Yes |
| Change field type | Yes |
| Remove an endpoint | Yes |
| Add required request field | Yes |
| Add optional request field | No |
| Add new field to response | No |
| Add a new endpoint | No |
| Add a new enum value | Potentially (if consumer switches on enum) |
```plantuml
@startuml
start
fork
:spectral lint\nopenapi.yaml;
if (lint errors?) then (yes)
:FAIL PR; <<#tomato>>
detach
endif
:Lint OK;
fork again
:oasdiff breaking\nmain vs PR branch;
if (breaking changes\nwithout new version?) then (yes)
:FAIL PR\n(bump to /v2/); <<#tomato>>
detach
endif
:Breaking Change OK;
fork again
:docker compose up;
:schemathesis run\n--checks all;
if (contract\nviolations?) then (yes)
:FAIL PR; <<#tomato>>
detach
endif
:Implementation OK;
end fork
:PR approved — merge allowed; <<#lightgreen>>
stop
@enduml
```
### oasdiff (CLI + CI)
```bash
# Install
go install github.com/tufin/oasdiff@latest
# Check for breaking changes between versions
oasdiff breaking api/v1/openapi-main.yaml api/v1/openapi-branch.yaml
# In CI (exit code 1 if breaking changes found)
oasdiff breaking \
https://raw.githubusercontent.com/org/repo/main/api/v1/openapi.yaml \
api/v1/openapi.yaml \
--fail-on ERR
```
### GitHub Actions CI
```yaml
# .github/workflows/api-contract.yml
name: API Contract
on: [pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Lint OpenAPI spec
run: npx @stoplight/spectral-cli lint api/v1/openapi.yaml
breaking-changes:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Detect breaking changes
uses: tufin/oasdiff-action@main
with:
base: "origin/main:api/v1/openapi.yaml"
revision: "api/v1/openapi.yaml"
fail-on-diff: true
validate-implementation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Start server
run: docker compose up -d --wait
- name: Run Schemathesis
run: |
pip install schemathesis
schemathesis run api/v1/openapi.yaml \
--base-url http://localhost:8080 \
--checks all
```
---
## Steps 6-7: Event-Driven APIs and Contract Testing
For AsyncAPI 3.0 (Kafka, NATS, SQS, WebSocket) and Consumer-Driven Contract Testing with Pact, see `api-contract-advanced`.
---
## Contract-First Checklist
Before writing implementation code for any new API surface:
- [ ] Spec written in `api/v1/openapi.yaml` (or `asyncapi.yaml` for events)
- [ ] All schemas use `$ref` — no inline schemas
- [ ] All error responses reference `ProblemDetails` schema
- [ ] `operationId` set on every operation
- [ ] `spectral lint` passes with zero errors
- [ ] Code generated from spec (types, stubs, or interfaces)
- [ ] Implementation only touches business logic — not request/response shapes
- [ ] `schemathesis` or `dredd` runs in CI against live server
- [ ] `oasdiff` configured in CI to block breaking changes without version bump
- [ ] New version directory (`api/v2/`) created for any breaking change
---
## Anti-Patterns
### Generate Spec from Code
```
# BAD: spec becomes a reflection of implementation details
springdoc.api-docs.enabled=true # auto-generates from annotations
```
The spec will drift the moment someone renames a method or adds an annotation. It becomes stale documentation, not a contract.
### Spec as Documentation Only
Writing the spec after the code, or keeping it in a Wiki/Notion — it will never be accurate or enforced.
### Skipping Validation in CI
If `schemathesis` or `oasdiff` does not run in CI, the contract is aspirational, not enforced.
### Shared Request/Response DTOs Between Services
Sharing Java/Go/TS types directly across services creates compile-time coupling. Share the spec file; generate independent types per service.
---
## Related Skills
- `api-design` — REST conventions (naming, status codes, pagination)
- `problem-details` — RFC 7807 error response implementation
- `strategic-ddd` — Published Language and Open Host Service patterns
- `hexagonal-typescript` / `hexagonal-java` — where generated types fit in the adapter layerRelated Skills
contract-testing
Contract Testing: Consumer-Driven Contract Testing with Pact (REST, messaging, Pact Broker, can-i-deploy), OpenAPI contract testing with Prism mock server and dredd, schema compatibility (ajv, Protobuf wire rules, Avro + Schema Registry), and breaking change detection with oasdiff in CI.
api-contract-advanced
Advanced API contract patterns — AsyncAPI 3.0 for event-driven systems and Consumer-Driven Contract Testing with Pact. Use after the core Contract-First workflow in api-contract.
zero-trust-patterns
Zero-Trust security patterns — mTLS between microservices (Istio/SPIFFE), SPIRE workload identity, OPA/Envoy authorization, NetworkPolicy default-deny-all, short-lived credentials, service mesh security, and Kubernetes RBAC hardening.
wireframing
Wireframing and prototyping workflow: fidelity levels (lo-fi sketch → mid-fi wireframe → hi-fi prototype), tool selection (Figma, Excalidraw, Balsamiq), user flow diagrams, wireframe annotation standards, information architecture (IA) mapping, and the handoff from wireframe to visual design. For developers who need to communicate UI structure before writing code.
webrtc-patterns
WebRTC patterns — peer connection setup, ICE/STUN/TURN configuration, signaling server design, SFU vs mesh topology, screen sharing, media track management, and reconnect/ICE restart handling.
webhook-patterns
Webhook patterns for receiving, verifying (HMAC), and idempotently processing third-party events. Covers Stripe, GitHub, and generic webhook patterns, delivery guarantees, retry handling, and testing.
web-performance
Web performance optimization: Core Web Vitals (LCP, CLS, INP), Lighthouse CI with budget configuration, bundle analysis (webpack-bundle-analyzer, vite-bundle-visualizer), hydration performance, network waterfall reading, image optimization (WebP/AVIF, srcset), and font performance.
wasm-performance
WebAssembly performance: wasm-opt binary optimization, size reduction (panic=abort, LTO, strip), profiling WASM in Chrome DevTools, memory management (linear memory, avoiding GC pressure), SIMD, and multi-threading with SharedArrayBuffer.
wasm-patterns
WebAssembly patterns: wasm-pack, wasm-bindgen (JS↔Wasm interop), WASI, Component Model, wasm-opt, Rust-to-WASM compilation, JS integration (web workers, streaming instantiation), and production deployment (CDN, Content-Type headers).
visual-testing
Visual Regression Testing: tool comparison (Chromatic/Percy/Playwright screenshots/BackstopJS), pixel-diff vs AI-based comparison, baseline management, flakiness strategies (masks, tolerances, waitForLoadState), CI integration with GitHub Actions, and Storybook integration.
visual-identity
Brand identity development: color palette construction (primary/secondary/semantic/neutral), logo concept brief writing, typeface pairings, brand voice definition, mood board direction, and Brand Guidelines document structure. Use when establishing or evolving a visual brand — not for implementing existing tokens.
ux-micro-patterns
UX micro-patterns for every product state: Empty States, Loading States (skeleton screens, spinners, optimistic UI), Error States, Success States, Confirmation Dialogs, Onboarding Flows, and Progressive Disclosure. These patterns apply to every feature — done wrong, they're the biggest source of user confusion.