Error Taxonomy Design: Turn Exceptions into Exceptional UX
8/13/2025
error-handling · api-design · user-experience · reliability
TL;DR: Most error systems only document what went wrong. Great error systems guide users to what they should do next.
The Error Message That Cost 10,000 Users
A payment API returned this during Black Friday:
{
"error": "INTERNAL_SERVER_ERROR",
"message": "Something went wrong"
}
10,000 checkout attempts failed. The real error? Rate limit exceeded. The fix? Wait 30 seconds. But the message revealed none of this.
Errors aren’t just failure notifications—they’re UX opportunities.
The Core Insight: Errors Are User Interfaces
Great error systems are conversation design. They should:
- Explain what happened in user terms
- Suggest specific next steps
- Provide context for debugging
- Maintain consistent interaction patterns
Mental Model: The Error Conversation Framework
Error Conversation:
1. ACKNOWLEDGE: "I understand what you tried to do"
2. EXPLAIN: "Here's what actually happened"
3. GUIDE: "Here's how to fix it"
4. CONTEXT: "Here's how to debug if needed"
Implementation: From Cryptic Failures to Helpful Guidance
User-Action Taxonomy
enum UserErrorType {
USER_FIXABLE = "USER_FIXABLE", // User can fix immediately
TEMPORARY_ISSUE = "TEMPORARY_ISSUE", // User needs to wait/retry
ACCESS_LIMITATION = "ACCESS_LIMITATION", // User needs different permissions
SYSTEM_ISSUE = "SYSTEM_ISSUE" // Contact support
}
interface StructuredError {
code: string; // Stable identifier
type: UserErrorType; // What user should do
title: string; // Human-readable explanation
detail: string;
userAction: {
immediate: string; // What to do right now
followUp?: string; // If immediate action fails
documentation?: string;
};
debugInfo: {
correlationId: string;
timestamp: string;
context?: Record<string, unknown>;
};
retryAfter?: number; // Seconds to wait
retryable: boolean;
}
Context-Aware Error Generation
// Error factory that includes user context
class ErrorFactory {
static createPaymentError(reason: string, userContext: UserContext): StructuredError {
if (reason === 'INSUFFICIENT_FUNDS') {
return {
code: 'PAYMENT_INSUFFICIENT_FUNDS',
type: UserErrorType.USER_FIXABLE,
title: 'Payment method declined',
detail: `Your ${userContext.paymentMethod.type} ending in ${userContext.paymentMethod.last4} was declined for insufficient funds.`,
userAction: {
immediate: 'Try a different payment method or add funds to your account',
followUp: 'Contact your bank if you believe this is an error'
},
retryable: true
};
}
if (reason === 'RATE_LIMITED') {
return {
code: 'PAYMENT_RATE_LIMITED',
type: UserErrorType.TEMPORARY_ISSUE,
title: 'Too many payment attempts',
detail: 'You\'ve exceeded the maximum number of payment attempts.',
userAction: {
immediate: 'Wait 5 minutes before trying again',
followUp: 'Contact support if you continue having issues'
},
retryAfter: 300,
retryable: true
};
}
}
}
Real-World Impact: Stripe’s Error Excellence
Before: {"error": "card_declined", "message": "Your card was declined."}
After: Contextual, actionable errors with decline codes, recovery suggestions, and localized messages.
Results:
- 67% reduction in support tickets
- 15% improvement in payment recovery rates
- $2.3B additional payments processed
Your Error Excellence Action Plan
- Today: Audit your most common error message and rewrite it with user guidance
- This week: Add correlation IDs and retry semantics
- This month: Implement error analytics to continuously improve
Remember: Every error is a conversation with your user. Make it helpful.
References & Deep Dives
- Stripe API Error Handling - Industry gold standard
- Microsoft REST API Guidelines - Error response patterns