21 KiB
API Contract Sync Manager - Examples
Real-world usage scenarios and code samples for common API contract synchronization tasks.
Example 1: Validating an OpenAPI Spec Against Express.js Routes
Scenario
You have an Express.js backend with an OpenAPI spec, and you want to verify that all documented endpoints are actually implemented.
OpenAPI Spec (openapi.yaml)
openapi: 3.0.0
info:
title: User API
version: 1.0.0
paths:
/api/users:
get:
summary: List users
operationId: listUsers
responses:
'200':
description: Success
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
post:
summary: Create user
operationId: createUser
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserRequest'
responses:
'201':
description: Created
/api/users/{id}:
get:
summary: Get user by ID
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'200':
description: Success
delete:
summary: Delete user
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'204':
description: Deleted
components:
schemas:
User:
type: object
properties:
id:
type: string
email:
type: string
name:
type: string
CreateUserRequest:
type: object
required:
- email
properties:
email:
type: string
name:
type: string
Express.js Implementation (routes/users.js)
const express = require('express');
const router = express.Router();
// GET /api/users - List users
router.get('/users', async (req, res) => {
const users = await db.users.findAll();
res.json(users);
});
// POST /api/users - Create user
router.post('/users', async (req, res) => {
const { email, name } = req.body;
const user = await db.users.create({ email, name });
res.status(201).json(user);
});
// GET /api/users/:id - Get user by ID
router.get('/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
});
// Note: DELETE /api/users/:id is NOT implemented
module.exports = router;
Usage
User asks: "Does our Express API implementation match the OpenAPI spec?"
Analysis Process:
- Read and parse
openapi.yaml - Extract endpoints:
- GET /api/users
- POST /api/users
- GET /api/users/{id}
- DELETE /api/users/{id}
- Search for Express route handlers using Grep
- Match implementations to spec
Result Report:
API Implementation Coverage Report
===================================
✓ GET /api/users - IMPLEMENTED
File: routes/users.js:5
✓ POST /api/users - IMPLEMENTED
File: routes/users.js:11
✓ GET /api/users/:id - IMPLEMENTED
File: routes/users.js:18
✗ DELETE /api/users/:id - MISSING
Documented in spec but not found in implementation
Expected location: routes/users.js
Coverage: 75% (3/4 endpoints)
Recommendation:
Implement the missing DELETE endpoint or remove it from the spec if it's not needed.
Example 2: Detecting Breaking Changes Between API Versions
Scenario
You're preparing to release API v2 and need to identify all breaking changes from v1 to inform client teams.
API v1 Spec
paths:
/api/users:
post:
requestBody:
content:
application/json:
schema:
type: object
required:
- email
properties:
email:
type: string
name:
type: string
responses:
'201':
content:
application/json:
schema:
$ref: '#/components/schemas/User'
components:
schemas:
User:
type: object
properties:
id:
type: string
email:
type: string
name:
type: string
phone:
type: string
API v2 Spec
paths:
/api/users:
post:
requestBody:
content:
application/json:
schema:
type: object
required:
- email
- role # NEW: Required field added
properties:
email:
type: string
name:
type: string
role:
type: string
enum:
- admin
- member
responses:
'201':
content:
application/json:
schema:
$ref: '#/components/schemas/User'
components:
schemas:
User:
type: object
properties:
id:
type: string
email:
type: string
name:
type: string
role:
type: string
# phone removed
Usage
User asks: "Compare API v1 and v2 specs and tell me about breaking changes"
Breaking Changes Detected:
API v2.0 Breaking Changes Report
=================================
🔴 BREAKING CHANGES (2)
1. POST /api/users - Required field added
Field: role
Type: string (enum: admin, member)
Impact: All existing POST /api/users requests will fail validation
Migration: Add "role" field to request body
2. User schema - Property removed
Property: phone
Impact: Clients reading user.phone will get undefined
Migration: Remove phone field usage or use alternative data source
⚠️ NON-BREAKING CHANGES (1)
1. User schema - Property added
Property: role
Type: string
Impact: Safe - clients will simply ignore this new field
RECOMMENDATION: This requires a MAJOR version bump (v1 → v2)
Client Migration Required:
- Update all POST /api/users calls to include role field
- Remove dependencies on user.phone property
- Update TypeScript interfaces if using typed clients
Generated Migration Guide:
# Migration Guide: API v1 → v2
## Breaking Changes
### 1. User Creation Requires Role
POST /api/users now requires a `role` field.
**Before (v1)**:
```json
{
"email": "user@example.com",
"name": "John Doe"
}
After (v2):
{
"email": "user@example.com",
"name": "John Doe",
"role": "member"
}
Action Required: Update all user creation calls to include role.
2. User Phone Field Removed
The phone field has been removed from the User object.
Before (v1):
const phone = user.phone;
After (v2):
// phone field no longer available
// Use contact service API if phone is needed
Action Required: Remove code that depends on user.phone.
Timeline
- Now: v1 and v2 both available
- March 1, 2026: v1 deprecated
- June 1, 2026: v1 removed
Testing
Test your integration against v2 at: https://api-v2.example.com
---
## Example 3: Generating TypeScript Types from OpenAPI
### Scenario
Your frontend team needs TypeScript interfaces for a newly documented API.
### OpenAPI Spec
```yaml
components:
schemas:
User:
type: object
required:
- id
- email
- role
properties:
id:
type: string
format: uuid
email:
type: string
format: email
name:
type: string
role:
type: string
enum:
- admin
- member
- guest
createdAt:
type: string
format: date-time
settings:
$ref: '#/components/schemas/UserSettings'
UserSettings:
type: object
properties:
theme:
type: string
enum:
- light
- dark
notifications:
type: boolean
CreateUserRequest:
type: object
required:
- email
- role
properties:
email:
type: string
name:
type: string
role:
type: string
enum:
- admin
- member
- guest
CreateUserResponse:
type: object
properties:
user:
$ref: '#/components/schemas/User'
token:
type: string
Usage
User asks: "Generate TypeScript types from the OpenAPI spec"
Generated TypeScript
// Auto-generated from OpenAPI spec
// Do not edit manually
/**
* User role enum
*/
export enum UserRole {
Admin = 'admin',
Member = 'member',
Guest = 'guest'
}
/**
* UI theme options
*/
export enum Theme {
Light = 'light',
Dark = 'dark'
}
/**
* User settings configuration
*/
export interface UserSettings {
theme?: Theme;
notifications?: boolean;
}
/**
* User entity
*/
export interface User {
/** UUID identifier */
id: string;
/** User email address */
email: string;
/** Optional display name */
name?: string;
/** User role */
role: UserRole;
/** Account creation timestamp */
createdAt?: Date;
/** User preferences */
settings?: UserSettings;
}
/**
* Request body for creating a new user
*/
export interface CreateUserRequest {
email: string;
name?: string;
role: UserRole;
}
/**
* Response from user creation
*/
export interface CreateUserResponse {
user?: User;
token?: string;
}
Additional Generation: API Client Functions
// API client with type safety
export class UserApiClient {
constructor(private baseUrl: string) {}
/**
* Create a new user
*/
async createUser(
data: CreateUserRequest
): Promise<CreateUserResponse> {
const response = await fetch(`${this.baseUrl}/api/users`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
}
/**
* Get user by ID
*/
async getUser(id: string): Promise<User> {
const response = await fetch(`${this.baseUrl}/api/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
}
/**
* List all users
*/
async listUsers(): Promise<User[]> {
const response = await fetch(`${this.baseUrl}/api/users`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
}
}
Example 4: Creating React Query Hooks from REST Endpoints
Scenario
Generate React Query hooks for type-safe data fetching.
OpenAPI Endpoints
paths:
/api/users/{id}:
get:
operationId: getUser
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/User'
/api/users:
post:
operationId: createUser
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserRequest'
responses:
'201':
content:
application/json:
schema:
$ref: '#/components/schemas/User'
Usage
User asks: "Generate React Query hooks from the API spec"
Generated Hooks
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
/**
* Hook to fetch a user by ID
* @param userId - User UUID
*/
export function useUser(userId: string) {
return useQuery({
queryKey: ['users', userId],
queryFn: async () => {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error('Failed to fetch user');
}
return response.json() as Promise<User>;
},
enabled: !!userId, // Only fetch if userId is provided
});
}
/**
* Hook to create a new user
*/
export function useCreateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (data: CreateUserRequest) => {
const response = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error('Failed to create user');
}
return response.json() as Promise<User>;
},
onSuccess: (newUser) => {
// Invalidate users list to refetch
queryClient.invalidateQueries({ queryKey: ['users'] });
// Optimistically update cache with new user
queryClient.setQueryData(['users', newUser.id], newUser);
},
});
}
/**
* Hook to list all users
*/
export function useUsers() {
return useQuery({
queryKey: ['users'],
queryFn: async () => {
const response = await fetch('/api/users');
if (!response.ok) {
throw new Error('Failed to fetch users');
}
return response.json() as Promise<User[]>;
},
});
}
Usage in React Components
// List users
function UserList() {
const { data: users, isLoading, error } = useUsers();
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<ul>
{users?.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
// Get single user
function UserProfile({ userId }: { userId: string }) {
const { data: user, isLoading } = useUser(userId);
if (isLoading) return <div>Loading...</div>;
if (!user) return <div>User not found</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
<p>Role: {user.role}</p>
</div>
);
}
// Create user
function CreateUserForm() {
const createUser = useCreateUser();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
await createUser.mutateAsync({
email: 'user@example.com',
name: 'John Doe',
role: UserRole.Member,
});
alert('User created!');
} catch (error) {
alert('Failed to create user');
}
};
return (
<form onSubmit={handleSubmit}>
{/* form fields */}
<button type="submit" disabled={createUser.isPending}>
{createUser.isPending ? 'Creating...' : 'Create User'}
</button>
</form>
);
}
Example 5: GraphQL Schema Evolution Best Practices
Scenario
Your GraphQL schema needs to evolve, and you want to ensure backward compatibility.
Current Schema (v1)
type User {
id: ID!
email: String!
name: String!
avatar: String
createdAt: String!
}
type Query {
user(id: ID!): User
users: [User!]!
}
type Mutation {
createUser(email: String!, name: String!): User!
}
Proposed Schema (v2)
scalar DateTime
scalar URL
type User {
id: ID!
email: String!
# Deprecated: use 'displayName' instead
name: String! @deprecated(reason: "Use 'displayName' for consistency")
displayName: String!
avatar: URL
createdAt: DateTime!
# New field (non-breaking)
updatedAt: DateTime
}
input CreateUserInput {
email: String!
name: String! # Keep for backward compatibility
displayName: String # New optional field
}
type Query {
user(id: ID!): User
# Enhanced with pagination (non-breaking)
users(limit: Int = 10, offset: Int = 0): UserConnection!
}
type UserConnection {
nodes: [User!]!
totalCount: Int!
pageInfo: PageInfo!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
}
type Mutation {
# Updated to use input type (non-breaking if backward compatible)
createUser(input: CreateUserInput!): User!
}
Usage
User asks: "Review the GraphQL schema changes for breaking changes"
Analysis Report
GraphQL Schema Evolution Analysis
==================================
✅ NON-BREAKING CHANGES (5)
1. Added new field: User.displayName
- Type: String!
- Impact: None - clients can ignore new fields
2. Added new field: User.updatedAt
- Type: DateTime
- Impact: None - optional field
3. Deprecated field: User.name
- Reason: "Use 'displayName' for consistency"
- Impact: None - field still available
- Action: Clients should migrate to displayName over time
4. Enhanced Query.users with pagination
- Added arguments: limit, offset
- Changed return type: [User!]! → UserConnection!
- Impact: POTENTIALLY BREAKING if not handled correctly
- Mitigation: Keep old resolver temporarily
5. Added custom scalars
- DateTime, URL
- Impact: None - improved type safety
⚠️ POTENTIALLY BREAKING CHANGES (1)
1. Query.users return type changed
- Old: [User!]!
- New: UserConnection!
- Risk: Existing queries expecting array will break
RECOMMENDATION: This change needs careful migration
Suggested Migration Strategy:
```graphql
type Query {
# Keep old field for backward compatibility
users: [User!]! @deprecated(reason: "Use usersConnection for pagination")
# Add new field with different name
usersConnection(limit: Int = 10, offset: Int = 0): UserConnection!
}
This allows gradual migration without breaking existing clients.
### Recommended Final Schema (v2 - Backward Compatible)
```graphql
scalar DateTime
scalar URL
type User {
id: ID!
email: String!
name: String! @deprecated(reason: "Use 'displayName' for consistency")
displayName: String!
avatar: URL
createdAt: DateTime!
updatedAt: DateTime
}
input CreateUserInput {
email: String!
displayName: String!
}
type Query {
user(id: ID!): User
# Keep old version for backward compatibility
users: [User!]! @deprecated(reason: "Use 'usersConnection' for pagination support")
# Add new version with pagination
usersConnection(limit: Int = 10, offset: Int = 0): UserConnection!
}
type UserConnection {
nodes: [User!]!
totalCount: Int!
pageInfo: PageInfo!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
}
type Mutation {
createUser(input: CreateUserInput!): User!
}
Migration Timeline:
Phase 1 (Now): Deploy v2 with both old and new fields
- users (deprecated) → returns [User!]!
- usersConnection (new) → returns UserConnection!
Phase 2 (3 months): Monitor usage
- Track which clients still use deprecated fields
- Contact teams to migrate
Phase 3 (6 months): Remove deprecated fields
- Remove users query
- Remove User.name field
- Only usersConnection and displayName remain
Example 6: Finding Undocumented Endpoints
Scenario
You suspect some endpoints are implemented but not documented in the OpenAPI spec.
OpenAPI Spec
paths:
/api/users:
get:
summary: List users
/api/users/{id}:
get:
summary: Get user
Actual Implementation
// routes/users.js
router.get('/users', listUsers);
router.get('/users/:id', getUser);
router.post('/users', createUser); // Not in spec!
router.delete('/users/:id', deleteUser); // Not in spec!
// routes/admin.js
router.get('/admin/stats', getStats); // Not in spec!
router.post('/admin/reset', resetDatabase); // Not in spec!
Usage
User asks: "Find any API endpoints that exist in code but aren't documented"
Analysis Process
- Parse OpenAPI spec → extract documented endpoints
- Search codebase for route definitions (Grep for Express patterns)
- Compare and identify undocumented routes
Result Report
Undocumented Endpoints Report
==============================
Found 4 implemented endpoints not in OpenAPI spec:
1. POST /api/users
File: routes/users.js:15
Handler: createUser
Status: PRODUCTION (likely intentional)
Recommendation: Add to OpenAPI spec with proper schema
2. DELETE /api/users/:id
File: routes/users.js:23
Handler: deleteUser
Status: PRODUCTION (likely intentional)
Recommendation: Add to OpenAPI spec
3. GET /admin/stats
File: routes/admin.js:8
Handler: getStats
Status: INTERNAL (admin only)
Recommendation: Consider separate internal API spec or document with security note
4. POST /admin/reset
File: routes/admin.js:18
Handler: resetDatabase
Status: DANGEROUS (database reset)
Recommendation: Remove from production or add strict authorization
SUMMARY:
- 2 documented endpoints (50% coverage)
- 4 undocumented endpoints (50% missing)
Next Steps:
1. Add POST /users and DELETE /users/:id to public spec
2. Create separate admin-api.yaml for internal endpoints
3. Secure or remove dangerous /admin/reset endpoint
Summary
These examples demonstrate how the API Contract Sync Manager skill helps with:
- Validation - Ensuring specs match implementations
- Change Detection - Identifying breaking vs. non-breaking changes
- Code Generation - Creating type-safe clients and hooks
- Evolution - Managing schema changes over time
- Coverage - Finding undocumented or unimplemented endpoints
The skill adapts to your specific tech stack (Express, FastAPI, GraphQL, etc.) and provides actionable insights for maintaining healthy API contracts.