Error Handling
The AgentPhone API uses standard HTTP status codes and returns detailed error information as JSON.
Error response format
Most API errors follow this structure:
Every error body also carries a top-level request_id, the same value as the X-Request-Id response header. Quote it when you contact support or submit feedback about a call.
Some responses use a plain {"detail": "..."} body with no error code instead. This includes authentication errors (401/403), endpoint throttling (429 with a Retry-After header), the outbound-call concurrency cap (429, no Retry-After), insufficient balance (402), and number-related 409s. Robust error handling should check for both shapes.
HTTP status codes
Error codes
DELIVERY_UNCONFIRMED
The message may already have been sent, but its outcome could not be confirmed. Returned with HTTP 409. Do not automatically resend it. If a message ID or delivery update is available, use it to inspect the original attempt. Contact support if the outcome remains unknown. This is not a definitive delivery failure, and an absent history entry does not make resending safe.
VALIDATION_ERROR
Request validation failed. Check the details field for specific field errors.
Number limit reached
Each account has a self-serve number limit (see numbers.limit in GET /v1/usage). POST /v1/numbers returns HTTP 409 with a plain {"detail": "..."} body when you hit the cap. Contact us to increase your limit.
Insufficient balance
Your balance is too low to complete a paid action. Returned as HTTP 402 with a plain {"detail": "..."} body; the message describes the balance requirement (for example, the minimum needed to provision a number and your current balance). Add funds from the Billing page or enable auto-recharge. The funding requirement depends on number type; iMessage uses a separate card subscription. See billing.
Rate limiting (429)
429 responses come in three flavors:
- Endpoint throttles return a plain
{"detail": "Too many requests. Please try again in N seconds."}body with aRetry-Afterheader. Wait that long, then retry. - Call concurrency caps on
POST /v1/callsreturn a plain{"detail": "Concurrent outbound call limit reached (N). Please wait for an active call to end."}body with noRetry-Afterheader. This clears as your active calls end, so wait briefly and retry. - Messaging limits return the error envelope with a specific code and no
Retry-Afterheader.RATE_LIMITEDis transient; the cap codes (CONVERSATION_STREAK_LIMIT,CONVERSATION_AWAITING_REPLY,CONVERSATION_INACTIVE,OUTBOUND_LIMIT_REACHED,NEW_CONVERSATION_LIMIT_REACHED, described below) clear only when the recipient replies or a daily window resets, never on retry.
RATE_LIMITED
Sending too fast. Returned with HTTP 429. This one is transient: slow down and retry shortly. No Retry-After header is included.
PHONE_NUMBER_NOT_FOUND
The requested phone number doesn’t exist or you don’t have access to it.
CONVERSATION_STREAK_LIMIT
You’ve sent too many messages in a row to one contact without a reply. Returned with HTTP 429.
Retrying will not clear this. The count only resets when the contact replies, and the limit applies to that one conversation, so your other threads keep sending. See Messaging Rate Limits for details.
OUTBOUND_LIMIT_REACHED
You’ve reached the daily cap for messaging contacts who have never messaged your line. Returned with HTTP 429.
Replies do not consume the outbound-first daily quota, but other conversation and safety restrictions still apply. The standard ceiling is 50 new contacts/day/outbound line. Wait for the daily reset.
NEW_CONVERSATION_LIMIT_REACHED
You’ve reached the daily cap for starting new conversations. Returned with HTTP 429. Replies to existing conversations still go through, and the cap resets daily.
CONVERSATION_AWAITING_REPLY
Up to 3 messages can be sent to a brand-new recipient before they respond. Returned with HTTP 429. Retrying does not clear it; it unlocks when the recipient replies.
CONVERSATION_INACTIVE
The conversation has been inactive for over 14 days and its single re-engagement message was already sent. Returned with HTTP 429. It unlocks when the recipient replies.
INBOUND_ONLY
This line can’t send the first message to a new recipient. Returned with HTTP 422. Ask the recipient to message you first. An inbound subscription does not include initiating new conversations; use an outbound line when that capability is needed.
iMessage safety controls
See iMessage limits.
WHATSAPP_NOT_ENABLED
WhatsApp has been switched off for this account. Returned with HTTP 403. WhatsApp is on by default for everyone, so this only appears if it was disabled manually. Email founders@agentphone.to to have it restored.
WhatsApp send errors
WhatsApp sends are rejected by Meta rather than by a carrier, and the reason comes back in the error message. The ones worth handling in code:
Template sends outside the window are the single most common WhatsApp failure. Check capabilities.whatsappWindowExpiresAt on the conversation before a free-form send rather than discovering it from a 422.
Provider errors
SMS_PROVIDER_ERROR, MESSAGE_PROVIDER_ERROR, and TELEPHONY_PROVIDER_ERROR indicate a failure at an upstream messaging or telephony provider, usually returned with HTTP 502. These are typically temporary, but the operation may have executed before the failure. See the retry guidance below before resending.
Delivery failures (failureReason)
The errors above are API errors: your request was rejected. A message can also be accepted (200) and then fail later at the carrier. When that happens the message’s status becomes failed and failureReason carries a plain-English explanation.
failureReason is human-readable diagnostic text, not a stable machine-code contract. Store the message ID and status, display the reason, and handle unknown reasons conservatively.
Delivery failures arrive asynchronously. Inspect the original message in number or conversation message history. There is no documented customer delivery-status webhook or single-message GET endpoint; inbound agent.message events are not outbound delivery receipts.
Stop sending to this recipient
Retrying these without correcting the cause will fail again and can incur additional charges. Remove the recipient or fix the number.
Screen numbers with GET /v1/numbers/lookup before sending to avoid paying for these. A lookup costs a fraction of a message and returns line type and country.
Retry later
These reasons suggest a transient problem. Inspect the original attempt’s status before deciding to send again; ambiguous outcomes can produce duplicates.
Wait, do not retry immediately
Volume and rate limits. A same-day retry on a daily limit fails again. Do not assume all failed messages have the same billing treatment; some confirmed non-billable failures receive a credit. See failed messages and refunds.
The first two are daily limits and the second two are rate limits. Retrying within the same day only helps the rate limits. If you see the daily ones at volume, throttle rather than retry, to avoid repeat failed attempts and possible additional charges.
Fix your configuration
These will keep failing until something is changed on your account.
Blocked as spam
Content or sender reputation. Repeated spam blocks damage your sender standing, so change the message rather than resending it.
Content and routing
This list covers every reason currently emitted. New ones can appear as carriers change their reporting, so treat an unrecognised failureReason as non-retryable and log it rather than assuming it is safe to resend.
Handling errors
Check response status
Handle both body shapes: the error envelope and the plain {"detail": "..."} form.
Handle rate limits
Use an explicit retry decision rather than retrying every 429:
A failed network request, timeout, or 5xx on a write is a different case: the action may have executed. Reconcile its outcome before any manual resend.
Retry transient errors
For 429, 500, 502, 503, and 504 errors on reads (GET and HEAD), use bounded exponential backoff. Do not apply this policy to message sends, call creation, or other operations with side effects:
The API does not support customer-supplied idempotency keys. A timeout, connection loss, or 5xx on POST /v1/messages or POST /v1/calls can occur after the operation has already executed. Treat the outcome as unknown and do not automatically resend. An empty conversation history is not proof that the original request did not execute: it may still be in flight, or its record may not have been saved. A new POST is a separate send and can produce a duplicate. Only retry when a specific response establishes non-acceptance, such as the endpoint throttles described above.
The TypeScript SDK agentphone@1.0.23 automatically retries HTTP 408, 429, and 5xx twice, including message and call creation. Set maxRetries: 0 for sends, or on the client, to prevent this. Check the retry policy of your installed release; SDKs and versions do not all use the same defaults.

