| name | pre-dev-api-design |
| description | Use after TRD to define component contracts and interfaces, before selecting protocols/technologies, when you need clear integration specifications |
API/Contract Design - Defining Component Interfaces
Foundational Principle
Component contracts and interfaces must be defined before technology/protocol selection.
Jumping to implementation without contract definition creates:
- Integration failures discovered during development
- Inconsistent data structures across components
- Teams blocked waiting for interface clarity
- Rework when assumptions about contracts differ
- No clear integration test boundaries
The API Design answers: WHAT data/operations components expose and consume? The API Design never answers: HOW those are implemented (protocols, serialization, specific tech).
When to Use This Skill
Use this skill when:
- TRD has passed Gate 3 validation
- System has multiple components that need to integrate
- Building APIs (internal or external)
- Microservices, modular monoliths, or distributed systems
- Need clear contracts for parallel development
Mandatory Workflow
Phase 1: Contract Analysis (Inputs Required)
- Approved TRD (Gate 3 passed) - architecture patterns defined
- Approved Feature Map (Gate 2 passed) - feature interactions mapped
- Approved PRD (Gate 1 passed) - business requirements locked
- Identify integration points from TRD component diagram
- Extract data flows from Feature Map
Phase 2: Contract Definition
For each component interface:
- Define operations (what actions can be performed)
- Specify inputs (what data is required)
- Specify outputs (what data is returned)
- Define errors (what failure cases exist)
- Document events (what notifications are sent)
- Set constraints (validation rules, rate limits)
- Version contracts (how changes are managed)
Phase 3: Gate 4 Validation
MANDATORY CHECKPOINT - Must pass before proceeding to Data Modeling:
- All TRD component interactions have contracts
- Operations are clearly named and described
- Inputs/outputs are fully specified
- Error scenarios are documented
- Events are defined with schemas
- Constraints are explicit (validation, limits)
- Versioning strategy is defined
- No protocol specifics (REST/gRPC/GraphQL)
- No technology implementations
Explicit Rules
✅ DO Include in API Design
- Operation names and descriptions
- Input parameters (name, type, required/optional, constraints)
- Output structure (fields, types, nullable)
- Error codes and descriptions
- Event types and payloads
- Validation rules (format, ranges, patterns)
- Rate limits or quota policies
- Idempotency requirements
- Authentication/authorization needs (abstract)
- Contract versioning strategy
❌ NEVER Include in API Design
- HTTP verbs (GET/POST/PUT) or REST specifics
- gRPC/GraphQL/WebSocket protocol details
- URL paths or route definitions
- Serialization formats (JSON/Protobuf/Avro)
- Framework-specific code (middleware, decorators)
- Database queries or ORM code
- Infrastructure (load balancers, API gateways)
- Specific authentication libraries (JWT libraries, OAuth packages)
Abstraction Rules
- Operation: Say "CreateUser" not "POST /api/v1/users"
- Data Type: Say "EmailAddress (validated)" not "string with regex"
- Error: Say "UserAlreadyExists" not "HTTP 409 Conflict"
- Auth: Say "Requires authenticated user" not "JWT Bearer token"
- Format: Say "ISO8601 timestamp" not "time.RFC3339"
Rationalization Table
| Excuse | Reality |
|---|---|
| "REST is obvious, just document endpoints" | Protocol choice goes in Dependency Map. Define contracts abstractly. |
| "We need HTTP codes for errors" | Error semantics matter; HTTP codes are protocol. Abstract the errors. |
| "Teams need to see JSON examples" | JSON is serialization. Define structure; format comes later. |
| "The contract IS the OpenAPI spec" | OpenAPI is protocol-specific. Design contracts first, generate specs later. |
| "gRPC/GraphQL affects the contract" | Protocols deliver contracts. Design protocol-agnostic contracts first. |
| "We already know it's REST" | Knowing doesn't mean documenting prematurely. Stay abstract. |
| "Framework validates inputs" | Validation logic is universal. Document rules; implementation comes later. |
| "This feels redundant with TRD" | TRD = components exist. API = how they talk. Different concerns. |
| "URL structure matters for APIs" | URLs are HTTP-specific. Focus on operations and data. |
| "But API Design means REST API" | API = interface. Could be REST, gRPC, events, or in-process. Stay abstract. |
Red Flags - STOP
If you catch yourself writing any of these in API Design, STOP:
- HTTP methods (GET, POST, PUT, DELETE, PATCH)
- URL paths (/api/v1/users, /users/{id})
- Protocol names (REST, GraphQL, gRPC, WebSocket)
- Status codes (200, 404, 500)
- Serialization formats (JSON, XML, Protobuf)
- Authentication tokens (JWT, OAuth2 tokens, API keys)
- Framework code (Express routes, gRPC service definitions)
- Transport mechanisms (HTTP/2, TCP, UDP)
When you catch yourself: Replace protocol detail with abstract contract. "POST /users" → "CreateUser operation"
Gate 4 Validation Checklist
Before proceeding to Data Modeling, verify:
Contract Completeness:
- All component-to-component interactions have contracts
- All external system integrations have contracts
- All event/message contracts are defined
- Client-facing APIs are fully specified
Operation Clarity:
- Each operation has clear purpose and description
- Operation names follow consistent naming convention
- Idempotency requirements are documented
- Batch operations are identified where relevant
Data Specification:
- All input parameters are typed and documented
- Required vs. optional is explicit
- Output structures are complete
- Null/empty cases are handled
Error Handling:
- All error scenarios are identified
- Error codes/types are defined
- Error messages provide actionable guidance
- Retry/recovery strategies are documented
Event Contracts:
- All events are named and described
- Event payloads are fully specified
- Event ordering/delivery semantics documented
- Event versioning strategy defined
Constraints & Policies:
- Validation rules are explicit (format, range, pattern)
- Rate limits or quotas are defined
- Timeouts and deadlines are specified
- Backward compatibility strategy exists
Technology Agnostic:
- No protocol-specific details (REST/gRPC/etc)
- No serialization format specifics
- No framework or library names
- Can implement in any protocol
Gate Result:
- ✅ PASS: All checkboxes checked → Proceed to Data Modeling
- ⚠️ CONDITIONAL: Remove protocol details → Re-validate
- ❌ FAIL: Incomplete contracts → Add missing specifications
Contract Template
# API/Contract Design: [Project/Feature Name]
## Overview
- **TRD Reference**: [Link to approved TRD]
- **Feature Map Reference**: [Link to approved Feature Map]
- **Last Updated**: [Date]
- **Status**: Draft / Under Review / Approved
## Contract Versioning Strategy
- **Approach**: [e.g., Semantic versioning, Date-based, etc.]
- **Backward Compatibility**: [Policy for breaking changes]
- **Deprecation Process**: [How old contracts are sunset]
## Component Contracts
### Component: [Component Name]
**Purpose**: [What this component does - from TRD]
**Integration Points** (from TRD):
- Inbound: [Components that call this one]
- Outbound: [Components this one calls]
---
#### Operation: [OperationName]
**Purpose**: [What this operation does]
**Inputs**:
| Parameter | Type | Required | Constraints | Description |
|-----------|------|----------|-------------|-------------|
| userId | Identifier | Yes | Non-empty, UUID format | Unique user identifier |
| email | EmailAddress | Yes | Valid email format | User's email address |
| displayName | String | No | 3-50 chars, alphanumeric | Public display name |
| preferences | PreferenceSet | No | - | User preferences object |
**Input Validation Rules**:
- `email` must match pattern: `[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}`
- `displayName` must not contain profanity (filter list: [reference])
- `preferences.theme` must be one of: ["light", "dark", "auto"]
**Outputs** (Success):
| Field | Type | Nullable | Description |
|-------|------|----------|-------------|
| userId | Identifier | No | Created user's unique ID |
| createdAt | Timestamp | No | ISO8601 timestamp of creation |
| status | UserStatus | No | Account status: "active" | "pending_verification" |
**Output Structure Example** (abstract):
UserCreatedResponse { userId: Identifier createdAt: Timestamp status: UserStatus }
**Errors**:
| Error Code | Condition | Description | Retry? |
|------------|-----------|-------------|--------|
| InvalidEmail | Email format invalid | Provided email doesn't match format | No |
| EmailAlreadyExists | Email in use | Account with this email exists | No |
| RateLimitExceeded | Too many requests | Max 5 creates per hour per IP | Yes, after delay |
| ServiceUnavailable | Downstream failure | Dependency unavailable | Yes, with backoff |
**Idempotency**:
- Idempotent if called with same `email` within 5 minutes
- Returns existing user if already created
**Authorization**:
- Requires: Anonymous (public operation)
- Rate limited: 5 requests per hour per IP
**Related Operations**:
- Triggers Event: `UserCreated` (see Events section)
- May call: `SendVerificationEmail` (async)
---
#### Operation: [AnotherOperationName]
[Same structure as above]
---
## Event Contracts
### Event: UserCreated
**Purpose**: Notifies system that new user account was created
**When Emitted**: After successful user creation, before returning response
**Payload**:
| Field | Type | Nullable | Description |
|-------|------|----------|-------------|
| eventId | Identifier | No | Unique event identifier |
| timestamp | Timestamp | No | ISO8601 event timestamp |
| userId | Identifier | No | Created user's ID |
| email | EmailAddress | No | User's email (for notifications) |
| source | String | No | Registration source: "web" | "mobile" | "api" |
**Payload Structure Example** (abstract):
UserCreatedEvent { eventId: Identifier timestamp: Timestamp userId: Identifier email: EmailAddress source: String }
**Consumers**:
- Email Service (sends welcome email)
- Analytics Service (tracks signups)
- Audit Log Service (records event)
**Delivery Semantics**:
- At-least-once delivery
- Consumers must handle duplicates (idempotency required)
**Ordering**:
- No guaranteed ordering with other events
- Events for same `userId` are ordered
**Retention**:
- Events retained for 30 days in event store
---
### Event: [AnotherEvent]
[Same structure as above]
---
## Cross-Component Integration Contracts
### Integration: User Service → Email Service
**Purpose**: Send transactional emails to users
**Operations Used**:
- `SendEmail` (async, fire-and-forget)
- `GetEmailStatus` (query email delivery status)
**Contract Reference**: See Email Service component contracts
**Data Flow**:
UserService --[UserCreated event]--> EventBroker --[subscribe]--> EmailService EmailService --[SendEmail operation]--> EmailProvider
**Error Handling**:
- Email Service failures do NOT block User Service operations
- Retries handled by Email Service (3 attempts, exponential backoff)
- Dead-letter queue for permanent failures
---
### Integration: [Another Integration]
[Same structure as above]
---
## External System Contracts
### External System: Payment Gateway
**Purpose**: Process payments for user subscriptions
**Operations Exposed to Us**:
- `InitiatePayment`: Start payment transaction
- `CheckPaymentStatus`: Query transaction status
- `RefundPayment`: Reverse transaction
**Operations We Expose to Them**:
- `PaymentWebhook`: Receive payment status updates
**Contract Details**:
#### Operation: InitiatePayment (We call Them)
**Inputs**:
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| transactionId | Identifier | Yes | Our internal transaction ID |
| amount | MonetaryAmount | Yes | Amount in smallest currency unit (cents) |
| currency | CurrencyCode | Yes | ISO 4217 code (USD, EUR, etc.) |
| customerEmail | EmailAddress | Yes | Customer's email for receipt |
**Outputs**:
| Field | Type | Description |
|-------|------|-------------|
| paymentId | Identifier | Gateway's payment ID (store for status checks) |
| redirectUrl | URL | URL to redirect user for payment |
| expiresAt | Timestamp | Payment link expiration |
**Errors**:
| Error Code | Description |
|------------|-------------|
| InvalidAmount | Amount out of acceptable range |
| UnsupportedCurrency | Currency not supported |
| GatewayUnavailable | External service down |
---
#### Operation: PaymentWebhook (They call Us)
**Purpose**: Receive async payment status updates
**Inputs**:
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| paymentId | Identifier | Yes | Gateway's payment ID |
| status | PaymentStatus | Yes | "succeeded" | "failed" | "pending" |
| transactionId | Identifier | Yes | Our transaction ID (from InitiatePayment) |
| timestamp | Timestamp | Yes | Status update timestamp |
| signature | String | Yes | HMAC signature for verification |
**Outputs**:
| Field | Type | Description |
|-------|------|-------------|
| acknowledged | Boolean | Always true (confirms receipt) |
**Security**:
- Must verify HMAC signature before processing
- Signature algorithm: HMAC-SHA256
- Secret key: [stored in secrets management]
**Idempotency**:
- Must handle duplicate webhooks (same paymentId + status)
- Store processed webhook IDs for deduplication
---
## Data Type Definitions
### Custom Types
#### EmailAddress
- **Base Type**: String
- **Format**: Valid email format per RFC 5322
- **Constraints**: Max 254 characters, case-insensitive
- **Example**: "user@example.com"
#### Identifier
- **Base Type**: String
- **Format**: UUID v4
- **Constraints**: Non-empty, immutable
- **Example**: "550e8400-e29b-41d4-a716-446655440000"
#### Timestamp
- **Base Type**: String
- **Format**: ISO 8601 with timezone
- **Constraints**: UTC timezone, millisecond precision
- **Example**: "2025-10-23T16:45:00.123Z"
#### MonetaryAmount
- **Base Type**: Integer
- **Format**: Amount in smallest currency unit (cents, pence, etc.)
- **Constraints**: Non-negative, max value 9,223,372,036,854,775,807
- **Example**: 1999 (represents $19.99)
#### CurrencyCode
- **Base Type**: String
- **Format**: ISO 4217 three-letter code
- **Constraints**: Uppercase, exactly 3 characters
- **Example**: "USD", "EUR", "GBP"
#### UserStatus
- **Base Type**: Enum
- **Values**: "active", "suspended", "deleted", "pending_verification"
- **Description**: Current account status
---
## Naming Conventions
**Operations**:
- Use verb + noun format: `CreateUser`, `GetPayment`, `UpdateProfile`
- Be specific: `ArchiveUser` instead of `DeleteUser` if soft-delete
**Parameters**:
- Use camelCase: `userId`, `createdAt`, `displayName`
- Be descriptive: `subscriptionExpiresAt` not `expiry`
- Boolean parameters: prefix with `is`/`has`: `isActive`, `hasPermission`
**Events**:
- Use past tense: `UserCreated`, `PaymentProcessed`, `OrderShipped`
- Include entity: `OrderShipped` not just `Shipped`
**Errors**:
- Use noun + condition: `ResourceNotFound`, `InvalidInput`, `RateLimitExceeded`
- Be specific: `EmailAlreadyExists` not `DuplicateError`
---
## Rate Limiting & Quotas
### Per-Operation Limits
| Operation | Limit | Window | Scope |
|-----------|-------|--------|-------|
| CreateUser | 5 requests | 1 hour | Per IP address |
| GetUserProfile | 100 requests | 1 minute | Per user |
| UpdateProfile | 10 requests | 1 minute | Per user |
| SendPasswordReset | 3 requests | 1 hour | Per email |
### Quota Policies
- Free tier: 1,000 API calls per day
- Pro tier: 100,000 API calls per day
- Enterprise: Custom limits
### Exceeded Limit Behavior
- Return error: `RateLimitExceeded`
- Include retry info: `retryAfter` timestamp
- Do NOT process request
---
## Backward Compatibility Strategy
### Breaking Changes
**Definition**: Changes that require consumers to update
- Removing fields from outputs
- Adding required parameters to inputs
- Changing data types
- Renaming operations
**Process**:
1. Announce deprecation 90 days in advance
2. Support old + new contract in parallel
3. Monitor old contract usage
4. Remove old contract after 180 days
### Non-Breaking Changes
**Definition**: Changes consumers can ignore
- Adding optional parameters
- Adding new fields to outputs
- Adding new operations
- Adding new error codes
**Process**:
- Deploy immediately
- Document in changelog
- No consumer updates required
---
## Testing Contracts
### Contract Testing Strategy
- Use contract testing tools (language-agnostic)
- Provider tests verify contract implementation
- Consumer tests verify contract usage
- CI/CD validates contracts haven't broken
### Example Test Scenarios
**CreateUser Operation**:
- ✓ Valid input creates user successfully
- ✓ Duplicate email returns `EmailAlreadyExists`
- ✓ Invalid email returns `InvalidEmail`
- ✓ Missing required field returns `InvalidInput`
- ✓ Rate limit exceeded returns `RateLimitExceeded`
- ✓ Success emits `UserCreated` event
---
## Gate 4 Validation
**Validation Date**: [Date]
**Validated By**: [Person/team]
- [ ] All component contracts defined
- [ ] All operations have inputs/outputs
- [ ] Error scenarios documented
- [ ] Events fully specified
- [ ] External integrations covered
- [ ] No protocol specifics included
- [ ] Ready for Data Modeling (Gate 5)
**Approval**: ☐ Approved | ☐ Needs Revision | ☐ Rejected
**Next Step**: Proceed to Data Modeling (`pre-dev-data-model`)
Common Violations and Fixes
Violation 1: Protocol-Specific Details
❌ Wrong:
#### Operation: CreateUser
**Endpoint**: POST /api/v1/users
**Status Codes**:
- 201 Created
- 409 Conflict (email exists)
- 400 Bad Request
✅ Correct:
#### Operation: CreateUser
**Purpose**: Create new user account
**Inputs**: [userId, email, displayName]
**Outputs**: UserCreatedResponse
**Errors**:
- EmailAlreadyExists (email in use)
- InvalidInput (validation failure)
Violation 2: Implementation in Contract
❌ Wrong:
**Validation**:
```javascript
if (!/^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/.test(email)) {
throw new ValidationError("Invalid email");
}
✅ **Correct**:
```markdown
**Validation Rules**:
- `email` must match email format per RFC 5322
- Pattern: local@domain.tld
- Max length: 254 characters
Violation 3: Technology-Specific Types
❌ Wrong:
**Output**:
```json
{
"userId": "uuid",
"createdAt": "Date",
"profile": "Map<String, Any>"
}
✅ **Correct**:
```markdown
**Outputs**:
| Field | Type | Description |
|-------|------|-------------|
| userId | Identifier | UUID format |
| createdAt | Timestamp | ISO8601 timestamp |
| profile | ProfileObject | User profile data |
Confidence Scoring
Use this to adjust your interaction with the user:
Confidence Factors:
Contract Completeness: [0-30]
- All operations documented: 30
- Most operations covered: 20
- Significant gaps: 10
Interface Clarity: [0-25]
- Clear, unambiguous contracts: 25
- Some interpretation needed: 15
- Vague or conflicting: 5
Integration Complexity: [0-25]
- Simple point-to-point: 25
- Moderate dependencies: 15
- Complex orchestration: 5
Error Handling Coverage: [0-20]
- All scenarios documented: 20
- Common cases covered: 12
- Minimal coverage: 5
Total: [0-100]
Action:
80+: Generate complete contracts autonomously
50-79: Present options for user selection
<50: Ask clarifying questions about integration needs
Output Location
Always output to: docs/pre-development/api-design/api-contracts-[feature-name].md
After API Design Approval
- ✅ Lock the contracts - interfaces are now reference for implementation
- 🎯 Use contracts as input for Data Modeling (next phase:
pre-dev-data-model) - 🚫 Never add protocol specifics to contracts retroactively
- 📋 Keep contracts technology-agnostic until Dependency Map
Quality Self-Check
Before declaring API Design complete, verify:
- All TRD integration points have contracts
- Operations are clearly named and described
- Inputs are fully specified (type, required, constraints)
- Outputs are complete (all fields documented)
- Error scenarios are comprehensive
- Events have full payload specifications
- Validation rules are explicit
- Rate limits are defined
- Idempotency is documented where relevant
- Zero protocol specifics (REST/gRPC/etc)
- Zero implementation code
- Contracts are testable
- Backward compatibility strategy exists
- Gate 4 validation checklist 100% complete
The Bottom Line
If you wrote API contracts with HTTP endpoints or gRPC services, remove them.
Contracts are protocol-agnostic. Period. No REST. No GraphQL. No HTTP codes.
Protocol choices go in Dependency Map. That's a later phase. Wait for it.
Violating this separation means:
- You're locked into a protocol before evaluating alternatives
- Contracts can't be reused across different delivery mechanisms
- You can't objectively compare REST vs. gRPC vs. messaging
- Teams can't work in parallel with clear interface agreements
Define the contract. Stay abstract. Choose protocol later.