AI Returns the Same Error for Everything
AI generates error responses with: generic messages ('Something went wrong', 'Bad request', 'Internal server error'), no error codes (clients cannot programmatically handle errors), no field-level details (which field failed? what was expected?), inconsistent formats (string error in one endpoint, object in another), and no actionable guidance (what should the user or developer do?). Clients cannot distinguish a validation error from a server crash.
Modern error responses are: structured (consistent JSON format across all endpoints), coded (machine-readable error code like VALIDATION_ERROR for client logic), messaged (human-readable description for developers and users), detailed (field-level errors for forms, constraint details for business rules), and linked (docs URL pointing to the full error explanation and fix). AI generates none of these.
These rules cover: consistent error response structure, error code taxonomy, user-facing vs developer-facing messages, field-level validation details, and documentation links in error payloads.
Rule 1: Consistent Error Response Structure
The rule: 'Every error response follows the same structure: { "error": { "code": "VALIDATION_ERROR", "message": "Email is not valid", "details": { "field": "email", "value": "notanemail", "constraint": "must be valid email format" }, "requestId": "req_abc123", "docs": "https://docs.example.com/errors/validation-error" } }. The structure is identical for 400s, 401s, 403s, 404s, and 500s — only the content changes. Clients parse one format, handle all errors.'
For the error envelope: 'Always wrap errors in an { error: {} } envelope — never return a bare string or array. The envelope: identifies the response as an error (check for error key), is extensible (add fields without breaking clients), and is distinguishable from success responses (success has data key, error has error key). Pattern: { data } for success, { error } for failure — never mix both.'
AI generates: res.json({ message: 'error' }) in one endpoint, res.json('error') in another, res.json({ errors: ['error'] }) in a third. Clients need three different parsers. One consistent structure means one error handler for the entire API — write it once, use it everywhere.
- Consistent structure: { error: { code, message, details, requestId, docs } }
- Same format for all error status codes — 400, 401, 403, 404, 500
- Error envelope: always { error: {} }, never bare string or array
- Success: { data }, Error: { error } — never mix both in one response
- requestId in every error — enables cross-referencing with server logs
AI generates three different error formats across three endpoints. Clients need three parsers. One consistent structure — { error: { code, message, details, requestId, docs } } — means one error handler for the entire API. Write it once, never revisit.
Rule 2: Machine-Readable Error Code Taxonomy
The rule: 'Define a finite set of error codes that clients can switch on: VALIDATION_ERROR, AUTHENTICATION_REQUIRED, PERMISSION_DENIED, RESOURCE_NOT_FOUND, RATE_LIMITED, CONFLICT, INTERNAL_ERROR. Error codes are: stable (never change once published), documented (each code has a docs page), and actionable (the code tells the client what to do: retry, re-authenticate, fix input, contact support).'
For code naming: 'Use SCREAMING_SNAKE_CASE for error codes — consistent, unambiguous, conventional. Prefix with domain for large APIs: USER_NOT_FOUND, ORDER_ALREADY_SHIPPED, PAYMENT_DECLINED. The code is the machine contract — clients build switch statements on it. The message is the human contract — it can change, improve, or be translated without breaking clients.'
AI generates: no error codes. The client parses the message string: if (error.message.includes('not found')). When you improve the message from 'not found' to 'User not found — check the ID format', the client parsing breaks. Error codes are stable identifiers; messages are mutable descriptions. Clients use codes; humans read messages.
if (error.message.includes('not found')) — client logic based on message strings. Improve the message from 'not found' to 'User not found' and the parsing breaks. Error codes are the stable contract; messages are the mutable description.
Rule 3: User-Facing vs Developer-Facing Messages
The rule: 'Provide two message levels: message (safe to show to end users — clear, non-technical, actionable) and developerMessage (detailed, technical, for debugging). User message: "This email is already registered. Try signing in instead." Developer message: "Unique constraint violation on users.email: 'jane@example.com' already exists (constraint: users_email_unique)." The user message drives UX; the developer message drives debugging.'
For message writing: 'User messages should: explain what happened (not how), suggest what to do next, avoid technical jargon (no 'constraint violation', 'null pointer', or 'timeout'), and never expose internal details (no table names, SQL, or stack traces). Developer messages should: include the technical cause, reference the constraint or system, include relevant values (the email that conflicted), and help reproduce the issue.'
AI generates: 'Internal Server Error' — useless to both users and developers. The user does not know what happened or what to do. The developer does not know which system failed or why. Two messages — one for each audience — make errors actionable for everyone.
Rule 4: Field-Level Validation Error Details
The rule: 'For validation errors (400), include field-level details: { "error": { "code": "VALIDATION_ERROR", "message": "Request validation failed", "details": { "fields": { "email": { "message": "Must be a valid email", "value": "notanemail", "constraint": "email" }, "password": { "message": "Must be at least 8 characters", "value": null, "constraint": "minLength:8" } } } } }. All field errors at once — never one at a time. Clients map these to form fields for inline display.'
For the details object: 'The details object is type-specific: validation errors have fields, rate limiting has retryAfter and limit, conflict errors have existingResourceId and conflictingField. The details schema is documented per error code — clients know exactly what to expect for VALIDATION_ERROR vs RATE_LIMITED vs CONFLICT. This is the contract: code tells you the error type, details tells you the specifics.'
AI generates: { error: 'Invalid email' } — one field, one error, no structure. The form shows a generic error, the user fixes email, submits, discovers the password is too short too. Field-level details: all errors at once, mapped to inputs, fixed in one pass.
Rule 5: Documentation Links in Error Payloads
The rule: 'Every error response includes a docs URL pointing to the full explanation: { "docs": "https://docs.example.com/errors/rate-limited" }. The docs page includes: what this error means, common causes, how to fix it, code examples for handling it, and related errors. This turns every error into a learning opportunity — the developer clicks the link and gets a full solution, not just a status code.'
For error documentation pages: 'Each error code has a dedicated page: /errors/validation-error, /errors/rate-limited, /errors/authentication-required. The page structure: title (error code + name), description (what and why), common causes (bulleted list), fix instructions (step by step), code examples (request that triggers the error + corrected request), and related errors (links to similar error codes).'
AI generates errors with no documentation reference. The developer Googles the error message, finds a Stack Overflow answer from 2019 that may or may not apply. A docs link in the error payload: instant, authoritative, always up-to-date. The error teaches the developer how to fix it — zero external searching required.
A docs URL in every error response turns every failure into a learning opportunity. The developer clicks the link, sees: what happened, why, how to fix it, and a corrected code example. Zero Googling, zero Stack Overflow, zero guessing.
Complete Error Codes and Messages Rules Template
Consolidated rules for error codes and messages.
- Consistent structure: { error: { code, message, details, requestId, docs } }
- Machine-readable codes: VALIDATION_ERROR, RATE_LIMITED — stable, documented, actionable
- User message (non-technical, actionable) + developer message (technical, debuggable)
- Field-level validation details: all fields, all errors, mapped to form inputs
- Details object varies by error code — documented schema per code
- Docs URL in every error — links to cause, fix, code examples, related errors
- requestId for log correlation — trace the error through your system
- Never expose internal details to users — no SQL, no stack traces, no table names