Error Handling
Comprehensive guide to handling API errors.
Error Response Format
All errors return consistent JSON format:
{
"detail": "Error description explaining what went wrong"
}
HTTP Status Codes
2xx Success
200 - OK
Request succeeded.
{
"eligible": true,
"coverage_pct": 80.0
}
4xx Client Errors
400 - Bad Request
Invalid request format or missing required fields.
{
"detail": "Invalid request format: missing field 'patient_data'"
}
Common causes:
- Missing required fields
- Invalid field types
- Malformed JSON
- Invalid data format
Recovery:
- Check request structure against endpoint documentation
- Verify all required fields are present
- Validate data types (strings, numbers, booleans)
- Retry request
401 - Unauthorized
Invalid or missing API key.
{
"detail": "Unauthorized: Invalid API key"
}
Common causes:
- Missing Authorization header
- Expired API key
- Invalid API key
Recovery:
- Check API key is included in header:
Authorization: Bearer YOUR_KEY - Verify key hasn't expired
- Generate new key if needed
404 - Not Found
Requested resource doesn't exist.
{
"detail": "Procedure code PROC999 not found"
}
Common causes:
- Invalid procedure code
- Invalid drug code
- Unsupported endpoint
Recovery:
- Check procedure/drug code against reference tables
- Verify endpoint URL is correct
- Contact support for new codes
422 - Validation Error
Request body validation failed.
{
"detail": "Validation error: data_hash must be 64 hex characters, got 'invalid_hash'"
}
Common causes:
- Invalid data format (e.g., non-hex for hash)
- Field length violation
- Invalid IPFS CID format
Recovery:
- Check field format against specification
- Verify data constraints (length, character set)
- Validate encoding (Base64 for encrypted data, hex for hashes)
429 - Too Many Requests
Rate limit exceeded.
{
"detail": "Rate limit exceeded: 100 requests per minute. Try again in 45 seconds."
}
Headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1705330800
Common causes:
- Sending too many requests per minute
- Burst of requests
Recovery:
- Implement exponential backoff
- Cache results when possible
- Upgrade to higher rate limit tier
- Use batch endpoints for multiple requests
5xx Server Errors
500 - Internal Server Error
Server encountered unexpected condition.
{
"detail": "Eligibility check failed: ZK proof generation timed out"
}
Common causes:
- Proof generation timeout
- Blockchain connection error
- Database error
- Encryption/decryption failure
Recovery:
- Retry request after 30 seconds
- Check server status at
/statusendpoint - Contact support if error persists
503 - Service Unavailable
Server is temporarily unavailable.
{
"detail": "Service temporarily unavailable. Please try again later."
}
Common causes:
- API server down
- Database maintenance
- Blockchain node offline
- Deployment in progress
Recovery:
- Wait 30-60 seconds and retry
- Check status page
- Contact support
Error Handling Patterns
Retry Logic
Implement exponential backoff for transient errors:
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=4, max=10)
)
def call_api_with_retry(endpoint, payload):
response = requests.post(endpoint, json=payload)
response.raise_for_status() # Raise exception for non-2xx codes
return response.json()
try:
result = call_api_with_retry(
"http://localhost:8000/verify-eligibility",
payload
)
except Exception as e:
print(f"Failed after retries: {e}")
Error Classification
Classify errors by type for different handling:
def classify_error(response):
if response.status_code == 429:
return "rate_limit"
elif response.status_code in [500, 503]:
return "transient"
elif response.status_code in [400, 422]:
return "validation"
else:
return "unknown"
def handle_error(response):
error_type = classify_error(response)
if error_type == "rate_limit":
wait_time = int(response.headers.get('X-RateLimit-Reset', 60))
print(f"Rate limited. Wait {wait_time} seconds")
elif error_type == "transient":
print("Server error. Retrying...")
elif error_type == "validation":
print(f"Invalid request: {response.json()['detail']}")
else:
print("Unknown error")
Error Logging
Log errors for debugging and monitoring:
import logging
import json
logger = logging.getLogger(__name__)
def call_api_with_logging(endpoint, payload):
try:
response = requests.post(endpoint, json=payload)
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as e:
error_detail = e.response.json().get('detail', 'Unknown error')
logger.error(
f"API error",
extra={
"endpoint": endpoint,
"status_code": e.response.status_code,
"error": error_detail,
"payload": json.dumps(payload)
}
)
raise
Common Error Scenarios
Scenario 1: Decryption Failure
Error:
{
"detail": "Eligibility check failed: Decryption failed"
}
Cause: Encrypted data doesn't match decryption key
Solution:
# Ensure consistent encryption key
encryption_key = load_key_from_secure_storage()
# Verify data was encrypted with same key
cipher = Fernet(encryption_key)
try:
decrypted = cipher.decrypt(encrypted_data)
except Exception as e:
print(f"Decryption failed: {e}")
# Regenerate encrypted data with correct key
Scenario 2: Hash Mismatch
Error:
{
"detail": "Eligibility check failed: Data hash mismatch"
}
Cause: data_hash doesn't match hash of encrypted_data
Solution:
import hashlib
# Recompute hash correctly
patient_data = {...}
correct_hash = hashlib.sha256(
json.dumps(patient_data).encode()
).hexdigest()
payload = {
"patient_data": {
...
"data_hash": correct_hash # Use computed hash
}
}
Scenario 3: Invalid Procedure Code
Error:
{
"detail": "Procedure code PROC999 not found"
}
Solution:
# Check procedure code against reference
VALID_PROCEDURES = ['PROC001', 'PROC002', 'PROC003', ...]
if procedure_code not in VALID_PROCEDURES:
print(f"Invalid procedure: {procedure_code}")
print(f"Valid options: {VALID_PROCEDURES}")
# Use correct procedure code
Scenario 4: Rate Limit
Error:
{
"detail": "Rate limit exceeded: 100 requests per minute"
}
Solution:
import time
def api_call_with_rate_limit_handling(endpoint, payload):
response = requests.post(endpoint, json=payload)
if response.status_code == 429:
# Extract wait time from header
reset_time = int(response.headers.get('X-RateLimit-Reset'))
wait_seconds = reset_time - time.time()
print(f"Rate limited. Waiting {wait_seconds} seconds...")
time.sleep(wait_seconds)
# Retry request
return api_call_with_rate_limit_handling(endpoint, payload)
return response.json()
Scenario 5: Timeout
Error:
{
"detail": "Request timeout after 30 seconds"
}
Solution:
import requests
# Use timeout parameter
response = requests.post(
"http://localhost:8000/verify-eligibility",
json=payload,
timeout=30 # 30 seconds
)
# Implement circuit breaker pattern
from circuitbreaker import circuit
@circuit(failure_threshold=5, recovery_timeout=60)
def call_api_with_circuit_breaker(endpoint, payload):
return requests.post(endpoint, json=payload)
Monitoring and Alerting
Error Metrics to Track
metrics = {
"total_requests": 1000,
"successful": 950,
"failed": 50,
"error_rate": "5.0%",
"by_type": {
"validation": 20,
"transient": 15,
"rate_limit": 10,
"authorization": 5
},
"latency": {
"p50": 400, # milliseconds
"p95": 800,
"p99": 1200
}
}
Alert Thresholds
Set up alerts for:
- Error rate > 5%
- Response time > 5 seconds (p95)
- Sustained 5xx errors
- Rate limit hits
Best Practices
- Always handle errors gracefully - Never crash on API error
- Log all errors - For debugging and monitoring
- Implement retry logic - With exponential backoff
- Cache results - To reduce API calls
- Monitor error rates - Set up alerts
- Use circuit breakers - Prevent cascading failures
- Validate input - Before sending to API
- Document expected errors - For client developers