Error Codes
All API errors return a JSON body with error and optional code fields. Retryable errors include a Retry-After header.
Error response format
// HTTP 429 — rate limited
{
"error": "Rate limit exceeded. Try again after 60 seconds.",
"code": "RATE_LIMITED",
"retry_after": 60
}
// HTTP 422 — OTP expired
{
"error": "OTP has expired. Please request a new one.",
"code": "OTP_EXPIRED"
}
// HTTP 403 — missing scope
{
"error": "API key does not have the required scope: contacts:write",
"code": "FORBIDDEN"
}HTTP status codes
| Status | Code | Meaning | Example | Retry? |
|---|---|---|---|---|
| 400 | BAD_REQUEST | Missing or invalid request body fields | phone field missing or not E.164 format | No |
| 401 | UNAUTHORIZED | Missing or invalid API key | Authorization header absent or key revoked | No |
| 403 | FORBIDDEN | API key does not have the required scope | contacts:write scope missing for POST /contacts | No |
| 404 | NOT_FOUND | Resource does not exist | Contact ID not found in workspace | No |
| 409 | CONFLICT | Duplicate resource or conflicting state | Contact with same phone already exists | No |
| 422 | UNPROCESSABLE | Request understood but validation failed | OTP already verified, template not approved | No |
| 429 | RATE_LIMITED | Too many requests — rate limit exceeded | > 1000 req/hour or OTP rate limit hit | Yes |
| 500 | SERVER_ERROR | Unexpected server error | Database timeout or internal failure | Yes |
| 503 | SERVICE_UNAVAILABLE | Temporary outage or maintenance | WhatsApp Cloud API downstream issue | Yes |
WhatsApp OTP error codes
| code field | Description |
|---|---|
| OTP_EXPIRED | OTP TTL has passed (default 10 minutes) |
| OTP_INVALID | Code submitted does not match |
| OTP_MAX_ATTEMPTS | 5 failed attempts — request invalidated |
| OTP_RATE_LIMITED | > 5 OTPs to same phone in 10 minutes |
| OTP_WORKSPACE_LIMIT | > 1000 OTPs per workspace per hour |
| WHATSAPP_NOT_CONNECTED | No active WhatsApp number on workspace |
| INSUFFICIENT_CREDITS | Credit balance below required amount |
| TEMPLATE_NOT_APPROVED | WhatsApp template pending or rejected by Meta |
Handling errors in the SDK
import { createClient, EmacronAI CRMError } from '@emacrontechnologies/outreachagent';
const oa = createClient(process.env.OUTREACHAGENT_API_KEY!);
try {
const { verified } = await oa.otp.verify(requestId, code);
if (!verified) {
// Wrong code but not an error — handle gracefully
return res.json({ success: false, message: 'Incorrect OTP' });
}
// ✅ Verified — log user in
} catch (err) {
if (err instanceof EmacronAI CRMError) {
switch (err.code) {
case 'OTP_EXPIRED':
return res.status(400).json({ message: 'OTP expired, please request a new one' });
case 'OTP_MAX_ATTEMPTS':
return res.status(429).json({ message: 'Too many attempts, request a new OTP' });
case 'RATE_LIMITED':
return res.status(429).json({
message: 'Rate limit hit',
retry_after: (err.raw as { retry_after?: number })?.retry_after,
});
default:
console.error('EmacronAI CRM error:', err.status, err.code, err.message);
return res.status(500).json({ message: 'Verification failed' });
}
}
throw err; // re-throw non-EmacronAI CRM errors
}