Error Handling
Comprehensive guide to handling errors when using MarkdownAPI.io.
Error Response Format
All errors return JSON with a detail field:
{
"detail": "Descriptive error message"
}Validation errors (422) include field-level details:
{
"detail": [
{
"loc": ["body", "name"],
"msg": "field required",
"type": "value_error.missing"
}
]
}Common HTTP Status Codes
| Code | Meaning | Typical Cause | Action |
|---|---|---|---|
| 400 | Bad Request | Invalid input | Validate request data |
| 401 | Unauthorized | Invalid/missing auth | Check credentials |
| 403 | Forbidden | Insufficient permissions | Verify ownership |
| 404 | Not Found | Resource doesn’t exist | Check IDs |
| 409 | Conflict | Duplicate resource | Use unique names |
| 413 | Payload Too Large | File too large | Reduce file size |
| 422 | Unprocessable Entity | Validation error | Check field constraints |
| 429 | Too Many Requests | Rate limited | Implement backoff |
| 500 | Internal Server Error | Server error | Retry or contact support |
Error Handling Patterns
Python Exception Handling
import httpx
from typing import Optional
class MarkdownAPIError(Exception):
"""Base exception for MarkdownAPI errors."""
pass
class AuthenticationError(MarkdownAPIError):
"""Authentication failed."""
pass
class NotFoundError(MarkdownAPIError):
"""Resource not found."""
pass
class ConflictError(MarkdownAPIError):
"""Resource conflict (duplicate)."""
pass
async def upload_file_with_error_handling(
project_id: str,
file_path: str
) -> Optional[dict]:
"""Upload file with comprehensive error handling."""
try:
return await upload_file(project_id, file_path)
except httpx.HTTPStatusError as e:
status = e.response.status_code
if status == 401:
raise AuthenticationError("Invalid API key")
elif status == 404:
raise NotFoundError(f"Project {project_id} not found")
elif status == 409:
raise ConflictError("File already exists")
elif status == 429:
# Rate limited - implement exponential backoff
retry_after = int(e.response.headers.get("Retry-After", "60"))
print(f"Rate limited. Retry after {retry_after}s")
raise
elif status >= 500:
print(f"Server error: {status}")
raise
else:
print(f"Unexpected error: {status}")
raise
except httpx.TimeoutException:
print("Request timed out")
raise
except Exception as e:
print(f"Unexpected error: {e}")
raiseRetry Logic with Exponential Backoff
import asyncio
from typing import TypeVar, Callable
T = TypeVar('T')
async def retry_with_backoff(
func: Callable[..., T],
max_retries: int = 3,
initial_delay: float = 1.0,
max_delay: float = 60.0,
exponential_base: float = 2.0
) -> T:
"""Retry function with exponential backoff."""
delay = initial_delay
for attempt in range(max_retries):
try:
return await func()
except httpx.HTTPStatusError as e:
# Retry on rate limit or server errors
if e.response.status_code in (429, 500, 502, 503, 504):
if attempt < max_retries - 1:
print(f"Attempt {attempt + 1} failed. Retrying in {delay}s...")
await asyncio.sleep(delay)
delay = min(delay * exponential_base, max_delay)
else:
raise
else:
# Don't retry client errors
raise
# Usage
result = await retry_with_backoff(
lambda: upload_file(project_id, file_path),
max_retries=3
)TypeScript Error Handling
class MarkdownAPIError extends Error {
constructor(
message: string,
public statusCode: number,
public response?: any
) {
super(message);
this.name = 'MarkdownAPIError';
}
}
async function uploadFileWithErrorHandling(
projectId: string,
filePath: string
): Promise<FileResponse> {
try {
const response = await fetch(
`https://markdownapi.io/api/projects/${projectId}/files`,
{
method: 'POST',
headers: { 'Authorization': `Bearer ${process.env.MARKDOWN_API_KEY!}` },
body: createFormData(filePath),
}
);
if (!response.ok) {
const error = await response.json();
switch (response.status) {
case 401:
throw new MarkdownAPIError('Invalid API key', 401);
case 404:
throw new MarkdownAPIError(`Project ${projectId} not found`, 404);
case 409:
throw new MarkdownAPIError('File already exists', 409, error);
case 429:
const retryAfter = response.headers.get('Retry-After') || '60';
throw new MarkdownAPIError(`Rate limited. Retry after ${retryAfter}s`, 429);
default:
throw new MarkdownAPIError(
error.detail || 'Unknown error',
response.status,
error
);
}
}
return await response.json();
} catch (error) {
if (error instanceof MarkdownAPIError) {
throw error;
}
throw new MarkdownAPIError('Network or unexpected error', 0, error);
}
}Best Practices
- Specific exceptions: Catch specific errors, not generic
Exception - Retry transient errors: Retry 429, 500, 502, 503, 504
- Don’t retry client errors: Don’t retry 400, 401, 403, 404, 409
- Exponential backoff: Increase delay between retries
- Respect Retry-After: Honor
Retry-Afterheader on 429 - Log errors: Log errors with context for debugging
- User-friendly messages: Show user-friendly messages, not raw errors
- Timeout handling: Set appropriate timeouts and handle them
See Also
- API Reference - Complete API documentation
- Authentication - Authentication guide
Last updated on