API Error Contracts: The Missing Piece in Backend Reliability
Many APIs document success payloads but leave errors ad-hoc. Clients then implement fragile parsing logic and break on minor backend changes.
Step 1: Define typed error envelope
{
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests",
"retry_after_sec": 5
}
}
Step 2: Return stable codes, variable messages
return res.status(429).json({
error: { code: 'RATE_LIMITED', message: 'Request quota exceeded', retry_after_sec: 5 }
});
Step 3: Add contract tests for error shapes
expect(body.error.code).toBe('RATE_LIMITED');
expect(typeof body.error.retry_after_sec).toBe('number');
Pitfall
Using HTTP status code alone as client contract. You lose domain-specific handling paths.
Verification
- Error codes remain backward-compatible across releases.
- Clients can branch behavior using stable code fields.
- Contract tests fail when envelope shape changes.