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

StatusCodeMeaningExampleRetry?
400BAD_REQUESTMissing or invalid request body fieldsphone field missing or not E.164 formatNo
401UNAUTHORIZEDMissing or invalid API keyAuthorization header absent or key revokedNo
403FORBIDDENAPI key does not have the required scopecontacts:write scope missing for POST /contactsNo
404NOT_FOUNDResource does not existContact ID not found in workspaceNo
409CONFLICTDuplicate resource or conflicting stateContact with same phone already existsNo
422UNPROCESSABLERequest understood but validation failedOTP already verified, template not approvedNo
429RATE_LIMITEDToo many requests — rate limit exceeded> 1000 req/hour or OTP rate limit hitYes
500SERVER_ERRORUnexpected server errorDatabase timeout or internal failureYes
503SERVICE_UNAVAILABLETemporary outage or maintenanceWhatsApp Cloud API downstream issueYes

WhatsApp OTP error codes

code fieldDescription
OTP_EXPIREDOTP TTL has passed (default 10 minutes)
OTP_INVALIDCode submitted does not match
OTP_MAX_ATTEMPTS5 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_CONNECTEDNo active WhatsApp number on workspace
INSUFFICIENT_CREDITSCredit balance below required amount
TEMPLATE_NOT_APPROVEDWhatsApp 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
}

Next steps