Error Handling in APIs
Error Documentation
Read aloud is unavailable in this browser.
The Missing Chapter in Most API Documentation
Visit nearly any API documentation, and you'll find exhaustive coverage of success paths: endpoints, parameters, request/response schemas, authentication flows. Then look for error documentation. What you'll often find is... almost nothing. Perhaps a generic "Errors" section listing 400, 401, and 500 without context. Maybe a mention that "errors follow a standard format."
This documentation gap creates real problems. Developers discover error conditions through production incidents. Integration partners build brittle error handling because they don't know what to expect. Support tickets multiply because consumers can't self-diagnose failures.
Comprehensive error documentation is a competitive advantage. It signals maturity, builds trust, and reduces integration friction—yet it remains one of the most neglected aspects of API design.
By the end of this page, you will understand what error documentation should include, strategies for organizing error catalogs, techniques for keeping documentation current, and tools and formats that make error documentation discoverable and useful.
Why Error Documentation Matters
Error documentation directly impacts the success of API consumers and the cost of supporting your API. The investment pays dividends across multiple dimensions:
- •Faster Integration — Developers can anticipate failure modes and build robust error handling from day one, not discover them through trial and error.
- •Reduced Support Load — When consumers can self-diagnose errors, fewer tickets reach your support team. Each documented error code is a deflected ticket.
- •Better User Experiences — When integrators know what errors to expect, they can craft appropriate user-facing messages. 'Payment declined (error code 4242)' isn't helpful; documented guidance is.
- •Faster Debugging — Documented error codes with causes and solutions dramatically reduce mean time to resolution for production issues.
- •Professional Trust — Comprehensive documentation signals a mature, well-engineered API. Enterprise customers evaluate documentation quality when choosing vendors.
- •Contractual Clarity — Documented errors become part of the API's contract. Consumers know what to expect; you know what you've promised.
Each undocumented error generates multiple support interactions: the initial discovery, attempts to reproduce, escalation for investigation, communication of the resolution. A single paragraph of documentation can eliminate hundreds of hours of support over the API's lifetime.
What to Document for Each Error
Effective error documentation answers the questions developers ask when encountering an error. For each error code or condition, include:
| Component | Purpose | Example |
|---|---|---|
| Error Code | Unique identifier for programmatic handling | PAYMENT.CARD_DECLINED |
| HTTP Status | HTTP status code returned | 402 Payment Required |
| Summary | One-line description of the error | Payment method was declined by issuer |
| Description | Detailed explanation of why this occurs | This error occurs when the card issuer refuses the charge. Common reasons include... |
| Causes | Specific conditions that trigger this error | Insufficient funds, Card expired, Fraud detection triggered |
| Resolution Steps | How to fix or work around the error |
|
| Retryable? | Whether retrying might succeed | No - customer action required |
| Example Response | Actual JSON of the error response | Complete response body example |
| Related Errors | Similar errors consumers might confuse | See also: PAYMENT.CARD_EXPIRED, PAYMENT.INVALID_CARD |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
# PAYMENT.CARD_DECLINED **HTTP Status:** 402 Payment Required ## SummaryThe payment method was declined by the card issuer. ## DescriptionThis error occurs when the customer's card issuer refuses to authorize the transaction. The issuer does not provide detailed reasons to merchants for security reasons, but common causes include insufficient funds, suspected fraud, or card restrictions. ## Common Causes | Cause | Description ||-------|-------------|| Insufficient funds | Card/account lacks funds for this amount || Fraud prevention | Issuer's fraud detection blocked the transaction || Card restrictions | Card may not be authorized for this merchant type || Daily limit | Customer exceeded their daily spending limit | ## Resolution Steps 1. **Try a different payment method** — Use a different card or payment method2. **Contact the card issuer** — The customer should call their bank for details3. **Verify billing details** — Ensure address matches card registration4. **Try a smaller amount** — May help identify if it's a limit issue ## Retryable?**No** — Retrying with the same card will produce the same result. Customer action is required before retry. ## Example Response ```json{ "code": "PAYMENT.CARD_DECLINED", "message": "The card ending in 4242 was declined", "details": { "declineCode": "insufficient_funds", "cardLast4": "4242", "cardBrand": "visa" }, "requestId": "req_abc123", "timestamp": "2024-01-15T14:30:00Z"}``` ## Related Errors- [PAYMENT.CARD_EXPIRED](/errors/payment-card-expired) — Card past expiration- [PAYMENT.INVALID_CVV](/errors/payment-invalid-cvv) — CVV verification failed- [PAYMENT.INSUFFICIENT_FUNDS](/errors/payment-insufficient-funds) — Specific decline code when issuer provides itOrganizing Error Documentation
As your API grows, errors accumulate. Thoughtful organization makes documentation navigable and useful.
- •By Category/Domain — Group errors by functional area: Authentication, Payments, Users, Validation. Readers find errors relevant to what they're implementing.
- •By HTTP Status — Group by 4xx vs 5xx, or by specific status codes. Useful for developers who know the status but not the specific error.
- •By Endpoint — Document errors inline with each endpoint. Readers see all possible responses when implementing that endpoint.
- •Alphabetical Error Code List — A comprehensive reference when developers know the error code and need details.
- •Searchable Error Catalog — A searchable interface for large APIs with many error codes.
Recommended Approach: Multiple Views
The best API documentation provides multiple entry points to error information:
- Global Error Reference — Complete catalog of all error codes, organized by category
- Per-Endpoint Errors — Each endpoint lists errors it can return, linked to the global reference
- Quick Reference Table — One-page table of all codes with one-line descriptions
- Searchable Interface — Filter/search errors by code, keyword, or category
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
# Per-endpoint error documentation in OpenAPIpaths: /orders: post: summary: Create an order operationId: createOrder responses: '201': description: Order created successfully content: application/json: schema: $ref: '#/components/schemas/Order' '400': description: Invalid request content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: invalid_json: summary: Malformed request body value: code: "REQUEST.INVALID_JSON" message: "Request body is not valid JSON" '401': $ref: '#/components/responses/Unauthorized' '422': description: Validation failed content: application/json: schema: $ref: '#/components/schemas/ValidationErrorResponse' examples: missing_items: summary: Order has no items value: code: "VALIDATION.ORDER_EMPTY" message: "Order must contain at least one item" details: field: "items" invalid_quantity: summary: Invalid item quantity value: code: "VALIDATION.INVALID_QUANTITY" message: "Item quantity must be between 1 and 100" details: field: "items[0].quantity" value: 0 constraint: min: 1 max: 100 # Reusable error responsescomponents: responses: Unauthorized: description: Authentication required or failed content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: missing_token: value: code: "AUTH.TOKEN_MISSING" message: "Authorization header required" invalid_token: value: code: "AUTH.TOKEN_INVALID" message: "Authorization token is invalid"In-Context Error Documentation
Beyond standalone error pages, errors should be documented where developers encounter them—in endpoint documentation, SDK references, and even in the errors themselves.
- •Endpoint Response Tables — Each endpoint shows all possible response codes with brief descriptions and links to full docs.
- •Code Examples with Error Handling — Show how to handle errors in code samples, not just success paths.
- •SDK Exception Documentation — In typed SDKs, document exception classes with causes and resolution.
- •Error Response Links — Include documentation URLs directly in error response bodies.
- •Interactive API Explorers — Show error responses as real examples users can trigger in sandboxes.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
/** * Creates a new order in the system. * * @param items - Array of items to include in the order * @param shippingAddress - Destination address for delivery * @returns The created order with generated ID and status * * @throws {ValidationError} When items are empty or invalid * - `VALIDATION.ORDER_EMPTY`: No items provided * - `VALIDATION.INVALID_QUANTITY`: Quantity out of range (1-100) * - `VALIDATION.INVALID_SKU`: Unknown product SKU * * @throws {AuthenticationError} When authentication fails * - `AUTH.TOKEN_EXPIRED`: Refresh your token and retry * - `AUTH.TOKEN_INVALID`: Re-authenticate required * * @throws {PaymentError} When payment processing fails * - `PAYMENT.CARD_DECLINED`: Customer must use different card * - `PAYMENT.INSUFFICIENT_FUNDS`: Customer's card lacks funds * * @throws {InventoryError} When items are unavailable * - `INVENTORY.OUT_OF_STOCK`: Reduce quantity or remove item * - `INVENTORY.DISCONTINUED`: Item no longer available * * @example * // Handling errors appropriately * try { * const order = await client.orders.create({ * items: [{ sku: 'WIDGET-001', quantity: 2 }], * shippingAddress: address * }); * console.log('Order created:', order.id); * } catch (err) { * if (err instanceof ValidationError) { * // Show validation messages to user * displayErrors(err.details.errors); * } else if (err instanceof PaymentError) { * // Prompt for different payment method * showPaymentForm({ error: err.message }); * } else if (err instanceof InventoryError) { * // Update cart to reflect availability * refreshCart(); * } else { * // Unexpected error - log and show generic message * console.error('Order failed:', err); * showGenericError(); * } * } */async function createOrder( items: OrderItem[], shippingAddress: Address): Promise<Order>;Keeping Documentation Current
Outdated documentation is often worse than no documentation—it actively misleads consumers. Keeping error documentation current requires systematic processes:
- •Error as Code — Define errors in code with metadata that generates documentation. When error definitions change, docs change automatically.
- •CI Documentation Checks — Verify all error codes returned by the API are documented. Fail builds when undocumented errors appear.
- •Schema-Driven Generation — Use OpenAPI or similar schemas as the source of truth. Generate docs from schemas.
- •Review Error Changes — PR reviews should check: "Does this change add/modify/remove errors? Is documentation updated?"
- •Version Documentation — Track when errors were added, deprecated, or changed. Consumers on older versions need old docs.
- •Monitor Production Errors — Track error codes returned in production. Unknown codes indicate documentation gaps.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
// Errors defined as code with full documentation metadata// This single source generates API responses AND documentation export const Errors = { AUTH_TOKEN_EXPIRED: defineError({ code: "AUTH.TOKEN_EXPIRED", httpStatus: 401, // Documentation fields summary: "Authentication token has expired", description: ` Access tokens expire after a configurable period (default: 1 hour). When expired, requests fail with this error until a new token is obtained. `, causes: [ "Token was issued more than TTL ago", "Token was explicitly revoked" ], resolution: [ "Use refresh token to obtain new access token", "Re-authenticate if refresh token also expired" ], retryable: true, retryAfter: "After obtaining new token", // Links docs: "/errors/auth-token-expired", guide: "/guides/authentication#token-refresh", // Version tracking since: "1.0.0", deprecated: false, // Example response example: { expiredAt: "2024-01-15T14:00:00Z" }, // Related errors related: ["AUTH.TOKEN_INVALID", "AUTH.TOKEN_MISSING"] }), // ... more errors}; // Generate markdown docsfunction generateDocs(): string { return Object.values(Errors) .map(error => `## ${error.code} **HTTP Status:** ${error.httpStatus} ### Summary${error.summary} ### Description${error.description} ### Causes${error.causes.map(c => `- ${c}`).join('')} ### Resolution${error.resolution.map((r, i) => `${i + 1}. ${r}`).join('')} ### Retryable${error.retryable ? `Yes — ${error.retryAfter}` : 'No'} `).join('---');}Treat documentation completeness as a testable property. Just as you test that endpoints return correct responses, test that every returned error code has corresponding documentation. Automation catches gaps before users do.
Error Documentation Formats and Tools
Different tools and formats serve different documentation needs. Choose based on your existing toolchain and consumer preferences.
| Format/Tool | Best For | Considerations |
|---|---|---|
| OpenAPI/Swagger | REST APIs with generated docs | Standard format; broad tooling; can be verbose for complex errors |
| Markdown/Static Sites | Detailed, prose-heavy documentation | Flexible layout; good for guides and examples; manual maintenance |
| API Reference Platforms | Unified API documentation | ReadMe, Stoplight, Redocly; search and interactivity built-in |
| Error Lookup Endpoint | Runtime error discovery | GET /errors/{code} returns full docs; self-service debugging |
| SDK Documentation | Language-specific integration | Javadoc, TSDoc, etc.; exception classes fully documented |
| Status Pages | System-wide error status | For operational issues affecting many requests; not for per-request errors |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
// Error lookup API endpoint - self-service error documentationimport express from 'express';import { errorCatalog, ErrorDocumentation } from './docs/error-catalog'; const app = express(); // Initialize error indexconst errorIndex = new Map<string, ErrorDocumentation>( errorCatalog.map(e => [e.code, e])); // GET /errors - List all error codesapp.get('/errors', (req, res) => { const { category, httpStatus, search } = req.query; let results = [...errorCatalog]; if (category) { results = results.filter(e => e.category === category); } if (httpStatus) { results = results.filter(e => e.httpStatus === parseInt(httpStatus as string)); } if (search) { const term = (search as string).toLowerCase(); results = results.filter(e => e.code.toLowerCase().includes(term) || e.summary.toLowerCase().includes(term) || e.description.toLowerCase().includes(term) ); } res.json({ errors: results.map(e => ({ code: e.code, httpStatus: e.httpStatus, summary: e.summary, category: e.category, detailUrl: `/errors/${encodeURIComponent(e.code)}` })), categories: [...new Set(errorCatalog.map(e => e.category))], total: results.length });}); // GET /errors/:code - Full documentation for specific errorapp.get('/errors/:code', (req, res) => { const code = decodeURIComponent(req.params.code); const error = errorIndex.get(code); if (!error) { return res.status(404).json({ code: 'ERROR.NOT_FOUND', message: `No documentation found for error code: ${code}`, suggestion: 'Use GET /errors to list all known error codes' }); } res.json({ ...error, links: { self: `/errors/${encodeURIComponent(code)}`, related: error.relatedErrors.map( r => `/errors/${encodeURIComponent(r)}` ), humanReadable: `https://docs.example.com/errors/${error.code}` } });}); // Include error lookup in error responses themselvesfunction formatErrorResponse(error: ApiError, requestId: string) { return { code: error.code, message: error.message, details: error.details, requestId, timestamp: new Date().toISOString(), documentation: { url: `https://api.example.com/errors/${encodeURIComponent(error.code)}`, humanUrl: `https://docs.example.com/errors/${error.code}` } };}Error Changelog and Versioning
Errors evolve over time. New errors are introduced, old errors are deprecated, and error details change. Tracking these changes helps consumers maintain compatibility.
- •Document when errors were added — Include the API version that introduced each error. Consumers on older versions won't encounter new errors.
- •Document deprecations with alternatives — When retiring an error, explain what replaces it and when it will be removed.
- •Track detail field changes — If an error's
detailsstructure changes, note what was added/removed. - •Version your error documentation — Consumers on v1 of your API need v1 error docs, not v2.
- •Announce breaking error changes — Removing an error code or changing its meaning is breaking. Treat it like any other breaking change.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
# Error Changelog ## Version 2.3.0 (2024-01-15) ### New Errors - **PAYMENT.3DS_REQUIRED** — Returned when payment requires 3D Secure authentication. See [3D Secure Guide](/guides/3d-secure) for handling. - **USER.MFA_REQUIRED** — Returned when action requires MFA verification. Includes `mfaMethods` in details with available verification options. ### Deprecated Errors - **AUTH.SESSION_EXPIRED** — *Deprecated*. Will be removed in v3.0. Use `AUTH.TOKEN_EXPIRED` instead. Both are returned during transition period. ### Changed - **VALIDATION.FIELD_INVALID** — Now includes `suggestedValue` in details when the system can infer a correction (e.g., typo in country code). --- ## Version 2.2.0 (2023-11-01) ### New Errors - **RATE_LIMIT.QUOTA_EXHAUSTED** — Separate from `RATE_LIMIT.EXCEEDED`. Returned when monthly quota is exhausted (not rate-limited). Includes `quotaResetAt` timestamp. ### Breaking Changes - **VALIDATION.REQUIRED_FIELD** renamed to **VALIDATION.FIELD_REQUIRED** for consistency. Old code will not work. --- ## Migration Guide: v2.1 to v2.2 ### Error Code Rename If your code handles `VALIDATION.REQUIRED_FIELD`, update to `VALIDATION.FIELD_REQUIRED`: ```javascript// Before (v2.1)if (error.code === 'VALIDATION.REQUIRED_FIELD') { ... } // After (v2.2+)if (error.code === 'VALIDATION.FIELD_REQUIRED') { ... }```Never rename an error code in a minor or patch version. Consumers programmatically match on codes. A rename is a breaking change requiring a major version bump and migration guidance.
KEEP LEARNING
There’s more to understand.
You’re reading the introduction. Create a free account to read the complete lesson, save notes and keep track of your progress.