API Errors
Understanding and handling API error responses.
Error Response Format
All errors follow this format:
{
"error": {
"code": "error_code",
"message": "Human-readable description",
"details": {
// Optional additional context
}
}
}
HTTP Status Codes
| Status |
Meaning |
When |
200 |
OK |
Request succeeded |
201 |
Created |
Resource created successfully |
204 |
No Content |
Successful deletion |
400 |
Bad Request |
Invalid request format |
401 |
Unauthorized |
Missing or invalid API key |
403 |
Forbidden |
API key lacks permission |
404 |
Not Found |
Resource doesn't exist |
422 |
Unprocessable Entity |
Validation errors |
429 |
Too Many Requests |
Rate limit exceeded |
500 |
Internal Server Error |
Something went wrong on our end |
Common Error Codes
Authentication Errors
| Code |
Message |
Solution |
unauthorized |
API key is required |
Add X-API-Key header |
invalid_api_key |
Invalid API key |
Check your API key is correct |
revoked_api_key |
API key has been revoked |
Generate a new API key |
Validation Errors
{
"error": {
"code": "validation_error",
"message": "Validation failed",
"details": {
"email": ["is invalid", "has already been taken"],
"name": ["can't be blank"]
}
}
}
| Code |
Description |
validation_error |
One or more fields failed validation |
invalid_email |
Email format is invalid |
duplicate_email |
Email already exists |
missing_required_field |
Required field was not provided |
Resource Errors
| Code |
Description |
not_found |
Resource doesn't exist |
already_sent |
Issue has already been sent |
cannot_delete |
Resource cannot be deleted |
Rate Limiting
HTTP/1.1 429 Too Many Requests
{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded",
"details": {
"limit": 1000,
"remaining": 0,
"reset_at": "2025-01-10T13:00:00Z"
}
}
}
Feature/Tier Errors
| Code |
Description |
sending_disabled |
Newsletter is on free tier |
feature_not_available |
Feature requires upgrade |
no_subscribers |
Newsletter has no active subscribers |
Handling Errors
JavaScript Example
try {
const response = await fetch('https://dailydraft.ai/api/v1/subscriptions', {
method: 'POST',
headers: {
'X-API-Key': apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({ email: '[email protected]' })
});
if (!response.ok) {
const error = await response.json();
switch (error.error.code) {
case 'duplicate_email':
console.log('Subscriber already exists');
break;
case 'validation_error':
console.log('Validation errors:', error.error.details);
break;
case 'rate_limited':
const resetAt = new Date(error.error.details.reset_at);
console.log(`Rate limited. Retry after ${resetAt}`);
break;
default:
console.log('Error:', error.error.message);
}
}
} catch (e) {
console.log('Network error:', e);
}
Ruby Example
response = HTTParty.post(
'https://dailydraft.ai/api/v1/subscriptions',
headers: { 'X-API-Key' => api_key },
body: { email: '[email protected]' }.to_json
)
unless response.success?
error = response.parsed_response['error']
case error['code']
when 'duplicate_email'
puts 'Subscriber already exists'
when 'rate_limited'
retry_after = Time.parse(error['details']['reset_at'])
puts "Rate limited. Retry after #{retry_after}"
else
puts "Error: #{error['message']}"
end
end
Best Practices
- Always check status codes: Don't assume success
- Handle rate limits: Implement exponential backoff
- Parse error details: Use
details for field-level errors
- Log errors: Keep logs for debugging
- Graceful degradation: Don't crash on API errors
Need Help?
Getting unexpected errors? Email [email protected] with:
- The endpoint you're calling
- The request body (without sensitive data)
- The full error response
- Timestamp of the request