Error Codes
All API errors return a consistent JSON format:
{
"error": {
"code": "error_code",
"message": "Human-readable description",
"details": {}
},
"meta": {
"request_id": "req_xyz789"
}
}
Use the request_id when contacting support.
Authentication Errors
| Code | HTTP Status | Description | Resolution |
|---|---|---|---|
unauthorized | 401 | Missing or invalid API key | Check your X-API-Key header |
forbidden | 403 | API key lacks required permissions | Use a key with appropriate scopes |
ip_not_allowed | 403 | Request from non-allowlisted IP | Add the IP to your key's allowlist in Settings |
account_locked | 403 | Account is locked | Contact support@zyphr.dev |
account_suspended | 403 | Account suspended for policy violation | Contact support@zyphr.dev |
Validation Errors
| Code | HTTP Status | Description | Resolution |
|---|---|---|---|
validation_error | 422 | Request body failed validation | Check details for specific field errors |
invalid_email | 422 | Malformed email address | Ensure valid email format |
invalid_phone_number | 422 | Invalid phone number format | Use E.164 format (e.g., +14155551234) |
invalid_template | 422 | Template syntax error | Fix Handlebars syntax in your template |
invalid_url | 422 | Malformed URL provided | Ensure valid URL format |
invalid_json | 400 | Request body is not valid JSON | Check JSON syntax |
missing_required_field | 422 | Required field not provided | Include all required fields |
Rate Limiting
| Code | HTTP Status | Description | Resolution |
|---|---|---|---|
rate_limit_exceeded | 429 | Too many API requests | Back off and retry after retry_after seconds |
otp_send_rate_limit_exceeded | 429 | Too many OTP/MFA code requests | Wait before requesting another code |
login_rate_limit_exceeded | 429 | Too many login attempts | Wait before retrying |
Rate limit headers are included in every response:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1705312800
Not Found Errors
| Code | HTTP Status | Description | Resolution |
|---|---|---|---|
not_found | 404 | Requested resource doesn't exist | Check the resource ID |
template_not_found | 404 | Template ID doesn't exist | Verify the template_id |
subscriber_not_found | 404 | Subscriber doesn't exist | Create the subscriber first |
device_not_found | 404 | Device ID doesn't exist | Check device registration |
webhook_not_found | 404 | Webhook ID doesn't exist | Verify the webhook ID |
topic_not_found | 404 | Topic doesn't exist | Create the topic first |
Conflict Errors
| Code | HTTP Status | Description | Resolution |
|---|---|---|---|
conflict | 409 | Resource already exists | Use update instead of create |
duplicate_subscriber | 409 | Subscriber with this external_id exists | Use the existing subscriber or update it |
duplicate_device | 409 | Device token already registered | Token already registered for this user |
already_verified | 409 | Domain is already verified | No action needed |
Configuration Errors
| Code | HTTP Status | Description | Resolution |
|---|---|---|---|
sms_not_configured | 400 | Twilio credentials not set up | Configure Twilio in Settings > SMS |
push_not_configured | 400 | Push credentials not set up | Configure APNs/FCM in Settings > Push |
domain_not_verified | 400 | Sending domain not verified | Verify domain in Settings > Domains |
sender_not_configured | 400 | No default sender configured | Set a default sender in Settings |
MFA & Security Errors
| Code | HTTP Status | Description | Resolution |
|---|---|---|---|
mfa_required | 403 | MFA verification needed | Complete the MFA challenge |
invalid_otp | 422 | OTP code is incorrect | Enter the correct code |
otp_expired | 422 | OTP code has expired | Request a new code |
invalid_recovery_code | 422 | Recovery code is invalid | Check the code and try again |
mfa_already_enabled | 409 | MFA is already enabled | No action needed |
Password Errors
| Code | HTTP Status | Description | Resolution |
|---|---|---|---|
weak_password | 422 | Password doesn't meet requirements | Use at least 8 characters with mixed case, numbers, and symbols |
password_recently_used | 422 | Password was used recently | Choose a different password |
invalid_current_password | 422 | Current password is wrong | Enter the correct current password |
Payment & Billing Errors
| Code | HTTP Status | Description | Resolution |
|---|---|---|---|
payment_required | 402 | Plan limits exceeded or payment failed | Upgrade your plan or update payment method |
plan_limit_exceeded | 403 | Feature not available on current plan | Upgrade your plan |
Operation Failures
| Code | HTTP Status | Description | Resolution |
|---|---|---|---|
send_failed | 500 | Message failed to send | Retry the request |
provider_error | 502 | External provider (SES, Twilio, APNs) error | Retry; check provider status if persistent |
template_render_failed | 422 | Template rendering failed | Check template variables match the data provided |
suppressed_recipient | 422 | Recipient is on the suppression list | Remove from suppression list or skip |
Internal Errors
| Code | HTTP Status | Description | Resolution |
|---|---|---|---|
internal_error | 500 | Unexpected server error | Retry; contact support if persistent |
service_unavailable | 503 | Service temporarily unavailable | Retry with exponential backoff |
timeout | 504 | Request timed out | Retry the request |
Handling Errors
Retry Strategy
For transient errors (429, 500, 502, 503, 504), implement exponential backoff:
# Example: retry with backoff
for i in 1 2 4 8 16; do
response=$(curl -s -w "\n%{http_code}" -X POST https://api.zyphr.dev/v1/emails \
-H "X-API-Key: zy_live_your_key" \
-H "Content-Type: application/json" \
-d '{"to": "user@example.com", "subject": "Hello", "html": "<p>Hi!</p>"}')
status=$(echo "$response" | tail -1)
if [ "$status" -lt 400 ]; then
echo "Success"
break
fi
echo "Retrying in ${i}s..."
sleep $i
done
Idempotency
Use the Idempotency-Key header to safely retry requests without creating duplicates:
curl -X POST https://api.zyphr.dev/v1/emails \
-H "X-API-Key: zy_live_your_key" \
-H "Idempotency-Key: unique-request-id-123" \
-H "Content-Type: application/json" \
-d '{"to": "user@example.com", "subject": "Hello", "html": "<p>Hi!</p>"}'