code: z.enum(['INVALID_INPUT', 'RATE_LIMITED', 'NOT_FOUND', 'INTERNAL_FAILURE']),
message: z.string().min(1),
requestId: z.string().uuid(),
timestamp: z.string().datetime(),
});
export const IntegrationContract = z.object({
version: z.literal('v2'),
baseUrl: z.string().url(),
auth: z.object({
type: z.literal('bearer'),
header: z.literal('X-Api-Token'),
}),
constraints: z.object({
rateLimit: z.object({
requestsPerMinute: z.number().int().positive(),
burstAllowance: z.number().int().nonnegative(),
}),
pagination: PaginationSchema,
errorFormat: ApiErrorSchema,
}),
});
export type IntegrationContract = z.infer<typeof IntegrationContract>;
**Why this choice:** Validation-first types guarantee that the contract is executable. When the schema is the source of truth, documentation generators, client SDKs, and server middleware all derive from the same definition. This eliminates the documentation-implementation gap that causes most integration failures.
### Step 2: Surface Critical Constraints Upfront
Consumers lose attention after the third structural element. Place authentication, rate limits, error formats, and versioning at the top of the specification. Use a plain-language executive summary before diving into endpoint definitions.
```typescript
// packages/integration-contract/src/summary.ts
export const CONTRACT_SUMMARY = {
purpose: 'Defines the stable interface for the Order Processing Service',
included: [
'Standard CRUD operations for order resources',
'Idempotency key support for POST/PATCH requests',
'Cursor-based pagination for list endpoints',
],
excluded: [
'Real-time event streaming (use the Notification Gateway)',
'Bulk import/export (use the Data Pipeline API)',
'Custom business logic beyond resource validation',
],
criticalConstraints: {
authentication: 'Bearer token via X-Api-Token header',
rateLimiting: '60 requests/minute per tenant, 10 burst allowance',
versioning: 'Semantic versioning in URL path (/v2/...)',
deprecation: '90-day notice before endpoint removal',
},
};
Why this choice: Explicit inclusion/exclusion boundaries prevent scope creep. When consumers know exactly what is and isn't covered, they stop requesting features that belong to adjacent services. Critical constraints are non-negotiable; surfacing them immediately reduces misconfiguration incidents.
Step 3: Implement Deterministic Change Management
Vague statements like "changes may affect timelines" create ambiguity. Define exact triggers that require contract renegotiation, version bumps, or consumer migration.
// packages/integration-contract/src/change-policy.ts
export const CHANGE_TRIGGER_POLICY = {
breakingChanges: [
'Removing or renaming an endpoint',
'Changing response payload structure',
'Modifying authentication mechanism',
'Altering error code semantics',
],
nonBreakingChanges: [
'Adding optional request fields',
'Introducing new endpoints',
'Improving response performance',
'Adding new error codes with fallback handling',
],
migrationProtocol: {
noticePeriod: '90 days',
communicationChannel: 'developer-portal@internal',
enforcement: 'CI gate blocks deployment if breaking change lacks migration guide',
},
};
Why this choice: Explicit triggers remove subjective interpretation. When a team knows exactly what constitutes a breaking change, they can plan migrations proactively. The CI enforcement gate ensures policy compliance without manual oversight.
Step 4: Enforce Revision Boundaries and Scope Limits
Scope creep thrives on ambiguous revision policies. Define what counts as a modification versus a new feature, and enforce limits through contract validation.
// packages/integration-contract/src/scope-validator.ts
import { z } from 'zod';
export const RevisionBoundarySchema = z.object({
maxIncludedRevisions: z.literal(2),
revisionDefinition: z.string().default('Modification to existing field values or structure'),
newFeatureDefinition: z.string().default('Addition of previously undefined fields, endpoints, or business logic'),
escalationPath: z.string().default('Submit RFC to architecture-review channel'),
});
export function validateScopeCompliance(payload: unknown): boolean {
const result = RevisionBoundarySchema.safeParse(payload);
if (!result.success) {
console.warn('Scope boundary violation detected. Escalation required.');
return false;
}
return true;
}
Why this choice: Clear boundaries force consumers to self-police requests. When the contract explicitly defines revision limits and escalation paths, feature requests are routed correctly instead of bloating the core integration.
Pitfall Guide
1. The Auto-Generated Dump Fallacy
Explanation: Publishing raw OpenAPI/Swagger output without curation assumes consumers will parse machine-readable specs as easily as humans. In reality, auto-generated docs lack context, bury constraints, and omit operational realities like rate limits or error semantics.
Fix: Treat the spec as a product. Add an executive summary, highlight critical constraints, and maintain a separate consumer guide that explains operational behavior, not just endpoint signatures.
2. Implicit Versioning
Explanation: Relying on header-based or query-parameter versioning without explicit URL path declaration causes routing confusion and makes deprecation tracking nearly impossible.
Fix: Use path-based versioning (/v2/orders). Enforce version immutability in CI. Never modify a published version; create a new one instead.
3. Vague Error Semantics
Explanation: Returning generic 500 Internal Server Error or inconsistent error payloads forces consumers to implement fragile fallback logic. This increases support tickets and integration fragility.
Fix: Define a strict error schema with machine-readable codes, human-readable messages, request tracing IDs, and consistent HTTP status mapping. Validate error responses in contract tests.
4. Buried Rate Limits & Quotas
Explanation: Placing rate limiting documentation in appendices or README files means consumers discover limits only after hitting them in staging or production.
Fix: Declare rate limits in the contract summary, expose them via response headers (X-RateLimit-Limit, X-RateLimit-Remaining), and implement client-side backoff logic in the official SDK.
5. Missing Deprecation Schedule
Explanation: Removing endpoints without notice breaks downstream services and destroys trust. Teams assume "stable" means "permanent" unless explicitly told otherwise.
Fix: Publish a deprecation policy with fixed notice periods (e.g., 90 days). Use Sunset headers and automated migration guides. Block removal in CI until the notice period expires.
6. Over-Abstracted Types
Explanation: Using generic any, Record<string, unknown>, or deeply nested optional fields shifts validation responsibility to consumers. This causes runtime failures and inconsistent implementations.
Fix: Define strict, explicit types. Use discriminated unions for polymorphic responses. Validate all inputs at the boundary. Never expose internal implementation types in the contract.
7. Ignoring Consumer Feedback Loops
Explanation: Publishing a contract and never revisiting it based on consumer experience creates drift. Pain points accumulate until a major rewrite becomes necessary.
Fix: Implement consumer-driven contract testing. Track integration metrics (time-to-first-call, error rates, support tickets). Schedule quarterly contract reviews with downstream teams.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Internal microservice integration | Path-based versioning + strict Zod schemas | Reduces cross-team friction, enables automated validation | Low (shared tooling) |
| External partner API | OpenAPI + SDK generation + 90-day deprecation policy | Ensures compliance, reduces support overhead, builds trust | Medium (SDK maintenance) |
| Rapid prototyping / MVP | Loose contract with explicit "experimental" flag | Accelerates iteration while signaling instability | Low (technical debt deferred) |
| Regulated industry (finance/health) | Formal contract testing + audit trail + immutable versions | Meets compliance requirements, prevents liability | High (process overhead) |
Configuration Template
// packages/integration-contract/src/config.ts
import { z } from 'zod';
import { generateOpenApi } from 'zod-openapi';
export const ContractConfig = {
service: 'OrderProcessingService',
version: 'v2',
baseUrl: 'https://api.internal.example.com/v2',
auth: {
type: 'bearer' as const,
header: 'X-Api-Token',
},
constraints: {
rateLimit: {
requestsPerMinute: 60,
burstAllowance: 10,
},
pagination: {
defaultLimit: 20,
maxLimit: 100,
strategy: 'cursor' as const,
},
errorFormat: {
includeRequestId: true,
includeTimestamp: true,
codes: ['INVALID_INPUT', 'RATE_LIMITED', 'NOT_FOUND', 'INTERNAL_FAILURE'],
},
},
deprecation: {
noticePeriodDays: 90,
communicationChannel: 'developer-portal@internal',
sunsetHeader: true,
},
};
export function generateContractSpec() {
const schema = z.object({
version: z.literal(ContractConfig.version),
baseUrl: z.string().url(),
auth: z.object({
type: z.enum(['bearer', 'apiKey']),
header: z.string(),
}),
constraints: z.object({
rateLimit: z.object({
requestsPerMinute: z.number().int().positive(),
burstAllowance: z.number().int().nonnegative(),
}),
pagination: z.object({
defaultLimit: z.number().int().positive(),
maxLimit: z.number().int().positive(),
strategy: z.enum(['offset', 'cursor']),
}),
errorFormat: z.object({
includeRequestId: z.boolean(),
includeTimestamp: z.boolean(),
codes: z.array(z.string()),
}),
}),
});
return generateOpenApi(schema, {
title: `${ContractConfig.service} Integration Contract`,
version: ContractConfig.version,
servers: [{ url: ContractConfig.baseUrl }],
});
}
Quick Start Guide
- Initialize the contract package: Create a dedicated
packages/integration-contract directory. Install zod, zod-openapi, and @types/node.
- Define core schemas: Add authentication, pagination, error format, and constraint schemas to
src/types.ts. Export them as TypeScript types using z.infer.
- Generate the specification: Run
generateContractSpec() to produce an OpenAPI 3.1 document. Publish it to your internal developer portal.
- Add CI validation: Configure a GitHub Actions or GitLab CI step that runs
zod validation against all incoming requests and outgoing responses. Block merges if schemas drift.
- Onboard consumers: Share the executive summary, link to the generated spec, and provide a minimal TypeScript client example. Schedule a 30-minute walkthrough with downstream teams.