Skip to main content

Error Handling

This guide maps the errors you can see when calling the Agile Telecom SMS API to clear recovery actions. There are three places errors surface: HTTP submission, per-message submission status codes, and DLR final states.

1. HTTP-level Errors (Submission)

HTTPMeaningWhat to do
200Submission accepted (check per-message accepted and statusCode)Continue; expect DLRs
400Malformed requestFix the payload; don't retry as-is
401Invalid credentialsRe-check API key / Basic Auth
403IP not whitelistedAdd the calling IP to whitelist
404Wrong endpoint or unknown account-type URLUse the URL matching your account (legacy vs new)
408Request timeoutRetry with back-off
413Payload too largeSplit the messages array into smaller batches
415Wrong Content-TypeSet Content-Type: application/json
429Rate limit exceededExponential back-off, see Best Practices
5xxPlatform errorRetry with back-off; alert if it persists

A 200 response does not mean every individual destination was accepted — inspect the per-message statusCode in results.

2. Per-Message Submission Codes

Within a 200 response, each entry in results[] carries its own accepted and statusCode.

CodeMeaningAction
200Message acceptedWait for DLR
400Invalid destination or bodyFix and resend
401Sender ID not authorised for this destination/countryUse a different sender ID or pre-register it
402Insufficient creditTop up via the wholesale portal
403Destination blacklisted or opted-outRemove from list, do not retry
429Per-account/per-route throttlingSlow down sends to that route
500Internal errorRetry with back-off

3. DLR Final States

Once submitted, the operator returns a delivery report. The full mapping is in the Delivery Reports guide; the recovery view:

StatusCodeRecovery
DELIVERED0None — success
BUFFERED1Wait — operator will deliver or expire
EXPIRED2Optional: retry once after a delay if the message is still useful
REJECTED3Investigate sender ID / content; do not blindly retry
UNDELIVERABLE4Mark the number as bad; never retry
UNKNOWN5Treat as delivered or expired based on your tolerance for false negatives
FAILED6Retry once with back-off; if it persists, alert

Common Pitfalls

"Everything 401s after midnight." You rotated keys but the cron job still uses the old one. Fix: load keys from env on every restart and rotate via deployment.

"Some destinations silently dropped." Often the sender ID isn't registered for that country (Italy and India are common offenders). Check REJECTED DLRs and the per-message statusCode in the submission response.

"Customers complain SMS arrived 6 hours late." The handset was off and the operator buffered the message until power-on. Status was BUFFERED → DELIVERED. Lower the validity (if available on your account) for time-sensitive sends.

"Random 429s under low load." Check the granularity — limits apply per API key and sometimes per route. SMPP avoids most of these; see SMPP.

"Webhook keeps retrying." Your DLR endpoint isn't returning 200 (or your inbound endpoint isn't returning +OK) within 10–30 seconds. Move heavy work behind a queue.

Retry Strategy in Code

import time, requests

def send_with_retry(payload, headers, max_attempts=3):
backoff = 1.0
for attempt in range(1, max_attempts + 1):
r = requests.post(
"https://wholesale.agiletelecom.com/services/sms/send",
json=payload, headers=headers, timeout=10,
)
if r.status_code == 200:
return r.json()
if r.status_code in (408, 429) or 500 <= r.status_code < 600:
time.sleep(backoff)
backoff *= 2
continue
# 4xx other than 408/429 — don't retry, the request is bad.
r.raise_for_status()
raise RuntimeError("send_with_retry: exhausted retries")

Next Steps