Error Handling
SendComms uses conventional HTTP response codes to indicate the success or failure of an API request. Codes in the 2xx range indicate success, codes in the 4xx range indicate client errors, and codes in the 5xx range indicate server errors.
Error Response Structure
All error responses follow a consistent structure:
{
"success": false,
"error": {
"code": "ERROR_CODE", // Machine-readable error code
"message": "Human-readable description",
"details": { ... }, // Optional: additional context
"transaction_id": "txn_..." // Optional: for tracking
}
}HTTP Status Codes
| Code | Status | Description |
|---|---|---|
| 200 | OK | Request succeeded |
| 201 | Created | Resource created successfully |
| 400 | Bad Request | Invalid parameters or request format |
| 401 | Unauthorized | Invalid or missing API key |
| 402 | Payment Required | Insufficient account balance |
| 403 | Forbidden | Account suspended or permission denied |
| 404 | Not Found | Resource not found |
| 409 | Conflict | Request already in progress (idempotency) |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Unexpected server error |
| 503 | Service Unavailable | Service temporarily unavailable |
Client Error Codes
These errors indicate an issue with your request. Check the error message for how to fix it.
UNAUTHORIZED{
"success": false,
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid or missing API key"
}
}ACCOUNT_SUSPENDED{
"success": false,
"error": {
"code": "ACCOUNT_SUSPENDED",
"message": "Your account has been suspended. Please contact support."
}
}INSUFFICIENT_BALANCE{
"success": false,
"error": {
"code": "INSUFFICIENT_BALANCE",
"message": "Insufficient balance. Required: $0.0350, Available: $0.0100",
"details": {
"required": 0.035,
"available": 0.01
}
}
}RATE_LIMIT_EXCEEDED{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Please try again in 39 seconds.",
"limit": 5,
"remaining": 0,
"reset": 1766452500,
"retryAfter": 39
}
}INVALID_PHONE_NUMBER{
"success": false,
"error": {
"code": "INVALID_PHONE_NUMBER",
"message": "Invalid phone number. Use E.164 format (e.g., +233540800994)"
}
}INVALID_EMAIL{
"success": false,
"error": {
"code": "INVALID_EMAIL",
"message": "Invalid email address: notanemail"
}
}MESSAGE_TOO_LONG{
"success": false,
"error": {
"code": "MESSAGE_TOO_LONG",
"message": "Message too long. Maximum 1600 characters allowed, got 1750",
"details": {
"length": 1750,
"maxLength": 1600
}
}
}MISSING_FIELD{
"success": false,
"error": {
"code": "MISSING_FIELD",
"message": "Missing required field: to"
}
}Service Error Codes
These errors indicate a temporary issue with the service. Most are retryable after a short delay.
| Status | Code | Service | Description | Retryable |
|---|---|---|---|---|
| 503 | SMS_SEND_FAILED | SMS | Failed to send SMS message | Yes |
| 503 | EMAIL_SEND_FAILED | Failed to send email message | Yes | |
| 503 | DATA_PURCHASE_FAILED | Data | Failed to process data bundle purchase | Yes |
| 503 | AIRTIME_PURCHASE_FAILED | Airtime | Failed to process airtime purchase | Yes |
503 Service Unavailable
When you receive a 503 error, this typically indicates a temporary issue. Wait a few seconds and retry your request. Use the idempotency_key parameter to safely retry without risk of duplicate processing.
Best Practices
Check the Error Code
Use the error.code field for programmatic handling rather than parsing error messages.
Use Idempotency Keys for Retries
Always include an idempotency_key when retrying failed requests to prevent duplicate processing.
Implement Exponential Backoff
For rate limit (429) and service unavailable (503) errors, implement exponential backoff starting with a 1-second delay and doubling each retry.
Log Transaction IDs
Always log the transaction_id from error responses. This helps support quickly identify and resolve issues.
Retry Example
Here's how to implement safe retries with idempotency:
async function sendSMSWithRetry(to, message, maxRetries = 3) {
const idempotencyKey = crypto.randomUUID();
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetch('https://api.sendcomms.com/api/v1/sms/send', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
to,
message,
idempotency_key: idempotencyKey
})
});
const data = await response.json();
if (response.ok) {
return data; // Success!
}
// Don't retry client errors (4xx except 429)
if (response.status >= 400 && response.status < 500 && response.status !== 429) {
throw new Error(data.error?.message || 'Request failed');
}
// Retry on 429 (rate limit) or 5xx (server errors)
if (attempt < maxRetries) {
const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
await new Promise(r => setTimeout(r, delay));
continue;
}
} catch (error) {
if (attempt === maxRetries) throw error;
}
}
}