Error Handling¶
This page documents error handling patterns and response formats in the RAG Modulo API.
Error Response Format¶
All API errors return JSON responses with a consistent structure:
For validation errors with multiple issues:
{
"detail": [
{
"type": "missing",
"loc": ["body", "collection_id"],
"msg": "Field required"
},
{
"type": "extra_forbidden",
"loc": ["body", "invalid_field"],
"msg": "Extra inputs are not permitted"
}
]
}
HTTP Status Codes¶
Success Codes¶
| Code | Meaning | Usage |
|---|---|---|
| 200 | OK | Successful GET, PUT, PATCH |
| 201 | Created | Successful POST creating resource |
| 204 | No Content | Successful DELETE |
Client Error Codes¶
| Code | Meaning | Usage |
|---|---|---|
| 400 | Bad Request | Invalid request syntax or parameters |
| 401 | Unauthorized | Missing or invalid authentication |
| 403 | Forbidden | Valid auth but insufficient permissions |
| 404 | Not Found | Resource does not exist |
| 409 | Conflict | Resource conflict (duplicate, state) |
| 422 | Unprocessable Entity | Validation errors |
| 429 | Too Many Requests | Rate limit exceeded |
Server Error Codes¶
| Code | Meaning | Usage |
|---|---|---|
| 500 | Internal Server Error | Unexpected server error |
| 503 | Service Unavailable | Service temporarily unavailable |
Common Error Scenarios¶
Authentication Errors¶
Invalid Token¶
Request:
Response: 401 Unauthorized
Missing Token¶
Request:
Response: 401 Unauthorized
Expired Token¶
Request:
Response: 401 Unauthorized
Validation Errors¶
Missing Required Field¶
Request:
Response: 422 Unprocessable Entity
{
"detail": [
{
"type": "missing",
"loc": ["body", "collection_id"],
"msg": "Field required",
"input": null
},
{
"type": "missing",
"loc": ["body", "user_id"],
"msg": "Field required",
"input": null
}
]
}
Invalid Field Type¶
Request:
POST /api/search
{
"question": "What is ML?",
"collection_id": "not-a-uuid",
"user_id": "123e4567-e89b-12d3-a456-426614174000"
}
Response: 422 Unprocessable Entity
{
"detail": [
{
"type": "uuid_parsing",
"loc": ["body", "collection_id"],
"msg": "Input should be a valid UUID"
}
]
}
Extra Fields Not Allowed¶
Request:
POST /api/search
{
"question": "What is ML?",
"collection_id": "123e4567-e89b-12d3-a456-426614174000",
"user_id": "123e4567-e89b-12d3-a456-426614174000",
"invalid_field": "value"
}
Response: 422 Unprocessable Entity
{
"detail": [
{
"type": "extra_forbidden",
"loc": ["body", "invalid_field"],
"msg": "Extra inputs are not permitted"
}
]
}
Resource Not Found¶
Request:
Response: 404 Not Found
{
"detail": "Collection not found",
"error_code": "COLLECTION_NOT_FOUND",
"collection_id": "00000000-0000-0000-0000-000000000000"
}
Permission Errors¶
Request:
Response: 403 Forbidden
{
"detail": "You do not have permission to delete this collection",
"error_code": "INSUFFICIENT_PERMISSIONS"
}
Rate Limiting¶
Request (after exceeding rate limit):
Response: 429 Too Many Requests
{
"detail": "Rate limit exceeded. Try again in 60 seconds.",
"error_code": "RATE_LIMIT_EXCEEDED",
"retry_after": 60
}
Headers:
Server Errors¶
Internal Server Error¶
Response: 500 Internal Server Error
{
"detail": "An unexpected error occurred",
"error_code": "INTERNAL_ERROR",
"request_id": "req_abc123"
}
Service Unavailable¶
Response: 503 Service Unavailable
{
"detail": "Service temporarily unavailable. Please try again later.",
"error_code": "SERVICE_UNAVAILABLE"
}
Error Codes Reference¶
| Error Code | HTTP Status | Description |
|---|---|---|
| AUTHENTICATION_FAILED | 401 | Invalid credentials |
| TOKEN_EXPIRED | 401 | JWT token expired |
| INSUFFICIENT_PERMISSIONS | 403 | User lacks required permissions |
| COLLECTION_NOT_FOUND | 404 | Collection does not exist |
| DOCUMENT_NOT_FOUND | 404 | Document does not exist |
| PIPELINE_NOT_FOUND | 404 | Pipeline does not exist |
| VALIDATION_ERROR | 422 | Request validation failed |
| DUPLICATE_COLLECTION | 409 | Collection name already exists |
| RATE_LIMIT_EXCEEDED | 429 | Too many requests |
| INTERNAL_ERROR | 500 | Unexpected server error |
| SERVICE_UNAVAILABLE | 503 | Service temporarily down |
Client Error Handling¶
Python Example¶
import requests
from requests.exceptions import HTTPError, RequestException
def handle_api_request(url, method="GET", **kwargs):
"""Make API request with comprehensive error handling."""
try:
response = requests.request(method, url, **kwargs)
response.raise_for_status()
return response.json()
except HTTPError as e:
status_code = e.response.status_code
error_data = e.response.json()
if status_code == 401:
print("Authentication failed. Please login again.")
elif status_code == 403:
print("Permission denied:", error_data.get("detail"))
elif status_code == 404:
print("Resource not found:", error_data.get("detail"))
elif status_code == 422:
print("Validation errors:")
for error in error_data.get("detail", []):
print(f" - {error['loc']}: {error['msg']}")
elif status_code == 429:
retry_after = error_data.get("retry_after", 60)
print(f"Rate limited. Retry after {retry_after} seconds.")
else:
print(f"HTTP error {status_code}:", error_data.get("detail"))
return None
except RequestException as e:
print(f"Request failed: {e}")
return None
# Usage
result = handle_api_request(
"http://localhost:8000/api/search",
method="POST",
headers={"Authorization": f"Bearer {token}"},
json=search_query
)
JavaScript Example¶
async function handleApiRequest(url, options = {}) {
try {
const response = await fetch(url, options);
if (!response.ok) {
const errorData = await response.json();
switch (response.status) {
case 401:
console.error("Authentication failed");
break;
case 403:
console.error("Permission denied:", errorData.detail);
break;
case 404:
console.error("Resource not found:", errorData.detail);
break;
case 422:
console.error("Validation errors:", errorData.detail);
break;
case 429:
console.error(`Rate limited. Retry after ${errorData.retry_after}s`);
break;
default:
console.error(`Error ${response.status}:`, errorData.detail);
}
return null;
}
return await response.json();
} catch (error) {
console.error("Request failed:", error);
return null;
}
}
// Usage
const result = await handleApiRequest('/api/search', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(searchQuery)
});
Retry Strategies¶
Exponential Backoff¶
import time
from requests.exceptions import HTTPError
def api_call_with_retry(url, max_retries=3, **kwargs):
"""Make API call with exponential backoff retry."""
for attempt in range(max_retries):
try:
response = requests.request(**kwargs, url=url)
response.raise_for_status()
return response.json()
except HTTPError as e:
if e.response.status_code == 429:
# Rate limited - use Retry-After header
retry_after = int(e.response.headers.get('Retry-After', 60))
print(f"Rate limited. Waiting {retry_after}s...")
time.sleep(retry_after)
elif e.response.status_code >= 500:
# Server error - exponential backoff
wait_time = 2 ** attempt
print(f"Server error. Retrying in {wait_time}s...")
time.sleep(wait_time)
else:
# Client error - don't retry
raise
except requests.exceptions.RequestException:
if attempt < max_retries - 1:
wait_time = 2 ** attempt
print(f"Request failed. Retrying in {wait_time}s...")
time.sleep(wait_time)
else:
raise
raise Exception("Max retries exceeded")
Best Practices¶
- Always Check Status Codes: Don't assume 200 OK
- Handle Validation Errors: Display field-specific errors to users
- Implement Retry Logic: For rate limits and server errors
- Log Error Details: Include request_id for debugging
- Use Timeouts: Prevent hanging requests
- Graceful Degradation: Provide fallback behavior
See Also¶
- API Endpoints - Available endpoints
- API Schemas - Request/response schemas
- Authentication - Authentication guide
- Examples - API usage examples