Error Handling
All errors in the Sent API v3 follow a consistent JSON envelope format with structured error codes, making it easy to programmatically handle errors and troubleshoot issues.
Error Response Format
All errors follow a consistent JSON envelope:
{
"success": false,
"status": 404,
"error": {
"code": "RESOURCE_001",
"message": "Contact not found",
"doc_url": "https://docs.sent.dm/reference/api/error-catalog"
},
"meta": {
"request_id": "req_a1b2c3d4e5f60718",
"timestamp": "2026-01-15T10:30:00Z",
"version": "v3"
}
}Top-level Fields
| Field | Type | Description |
|---|---|---|
success | boolean | Always false on an error response |
status | integer | HTTP status code, repeated in the body |
error | object | The error itself, described below |
meta | object | Request metadata, described below |
An error response carries no data field.
Error Object Fields
| Field | Type | Description |
|---|---|---|
code | string | Machine-readable error code (for example, RESOURCE_001) |
message | string | Human-readable error message |
details | object | Field-level validation errors, as a map of field name to an array of messages. Omitted when there are none |
doc_url | string | Link to the documentation page for this class of error. Omitted when unset |
details and doc_url are omitted rather than sent as null, so read them defensively.
Meta Object Fields
| Field | Type | Description |
|---|---|---|
request_id | string | Unique request identifier for support and debugging |
timestamp | string | ISO 8601 timestamp of the error |
version | string | API version (always v3) |
HTTP Status Codes
| Status | Description | Common Causes |
|---|---|---|
200 OK | Request successful | - |
201 Created | Resource created successfully | - |
204 No Content | Request successful, no response body | DELETE operations |
400 Bad Request | Invalid request format or parameters | Missing required fields, invalid JSON |
401 Unauthorized | Authentication failed | Missing or invalid API key |
403 Forbidden | Permission denied | Insufficient role permissions |
404 Not Found | Resource not found | Invalid resource ID |
409 Conflict | Resource conflict | Duplicate entry, concurrent modification |
413 Payload Too Large | Request body too large | Body exceeds the endpoint's size limit |
422 Unprocessable Entity | Validation failed | Invalid field values, business rule violations, Idempotency-Key reused for a different request |
429 Too Many Requests | Rate limit exceeded | Too many requests in time window |
500 Internal Server Error | Unexpected server error | Server-side issue |
501 Not Implemented | Not supported yet | Compliance requirements requested for a channel other than SMS |
502 Bad Gateway | Upstream service error | Provider service unavailable |
503 Service Unavailable | Service temporarily unavailable | Maintenance or overload |
Error Code Reference
The Error Catalog is the canonical enumeration of every error code, with causes and step-by-step remediation. The codes below are the ones client code most commonly branches on:
| Code | HTTP Status | Meaning | Handling |
|---|---|---|---|
AUTH_001 | 401 | User is not authenticated | Renew the voice token. A missing or wrong API key returns AUTH_002 instead |
AUTH_002 | 401 | Invalid or missing API key | Send the key in x-api-key or Authorization: Bearer. Verify it, and regenerate it if revoked |
VALIDATION_001 | 400 | Request validation failed | Check the details field for field-level errors |
RESOURCE_001 | 404 | Contact not found | Verify the contact ID exists and belongs to your account |
BUSINESS_002 | 429 | Rate limit exceeded | Back off and respect the Retry-After header |
CONFLICT_001 | 409 | Concurrent idempotent request in progress | Wait for the original request to complete before retrying |
SERVICE_001 | 503 | Cache service temporarily unavailable | Retry the request after a short delay |
INTERNAL_001 | 500 | Unexpected internal server error | Retry, then contact support with the request ID |
For the full AUTH_*, VALIDATION_*, RESOURCE_*, BUSINESS_*, CONFLICT_*, SERVICE_*, and INTERNAL_* listings, refer to the Error Code Quick Reference in the Error Catalog.
Example Validation Error with Details (POST /v3/messages with an empty to array and a template missing both id and name):
{
"success": false,
"status": 400,
"error": {
"code": "VALIDATION_001",
"message": "Request validation failed",
"details": {
"to": ["'to' must contain at least one recipient"],
"template": ["'template' must have either 'id' (non-empty GUID) or 'name' (non-empty string)"]
},
"doc_url": "https://docs.sent.dm/reference/api/error-catalog"
},
"meta": { ... }
}Send-time Error Codes (ERR_*)
These codes are produced by the per-message send pipeline. They are not returned in the HTTP response. POST /v3/messages responds 202 for the batch, and each message is finalized asynchronously with a terminal status. The ERR_* code is Sent's internal classification: what you receive is the public reason_code it maps to, with a reason sentence beside it.
| Code | Terminal status | reason_code | What it means |
|---|---|---|---|
ERR_CONSENT_BLOCKED | FILTERED | BUSINESS_004 | The recipient's contact has opt_out = true, or their phone is on your phone-channel suppression list. The send is suppressed pre-routing: no provider call is made and no charge applies. |
ERR_ROUTE_DENIED | FILTERED | DELIVERY_004 | Routing rules denied every candidate route for this send, and no fallback was allowed. |
ERR_TEMPLATE_PARAMS_INVALID | FAILED | VALIDATION_008 | Required template variables were missing or failed validation. |
Read each message's outcome, and its reason_code and reason, through:
GET /v3/messages/{id}: thestatusfield carries the terminal status, andreason_codeandreasonsit beside it and on the matching eventGET /v3/messages/{id}/activities: theFILTEREDorFAILEDactivity carries the same pair- The
message.filteredandmessage.failedwebhook events, aspayload.reason_codeandpayload.reason
Every other way a message can end FAILED, FILTERED or BLOCKED is reported the same way. See Message Outcome Codes for the codes only an outcome can have.
Conversation-window codes
Two send-time codes are not ERR_* prefixed, because they are part of the two-way messaging contract and carry an actionable reason. Like the ERR_* codes, they are not returned on the HTTP response: POST /v3/messages responds 202 and the outcome lands on the message as its terminal status. Both call for an approved template as the first step, but they answer to different rules and a template does not clear both: on SMS, RCS, and auto-detect it opens a 7-day window for a contact who has never replied, while only a recent inbound WhatsApp message opens Meta's 24-hour one.
| Code | Terminal status | Trigger | Whose rule |
|---|---|---|---|
CONVERSATION_TEMPLATE_REQUIRED | BLOCKED | Free-form send on SMS, RCS, or auto-detect to a contact who has never replied, and either no template was ever sent or the last one is more than 7 days old. | Sent's |
WHATSAPP_TEMPLATE_REQUIRED | FAILED | Free-form send pinned to channel: "whatsapp" outside Meta's 24-hour customer-service window, or a Meta 131047 re-engagement rejection. | Meta's |
Each is its own reason_code, on the message and on its webhook. The terminal status differs between them: the conversation gate records BLOCKED (excluded from your deliverability rate, fires message.blocked), while the WhatsApp gate records FAILED. Full causes and remediation: CONVERSATION_TEMPLATE_REQUIRED and WHATSAPP_TEMPLATE_REQUIRED. Window mechanics: Conversation Windows.
Common Error Scenarios
Authentication Issues
Missing API Key
curl -X GET https://api.sent.dm/v3/me
# Response: 401 AUTH_002Solution: Send the API key, in the x-api-key header or as Authorization: Bearer:
curl -X GET https://api.sent.dm/v3/me \
-H "x-api-key: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"Validation Issues
Invalid Phone Number
A POST /v3/messages request with a recipient that is not in E.164 format returns:
{
"success": false,
"status": 400,
"error": {
"code": "VALIDATION_001",
"message": "Request validation failed",
"details": {
"to": ["Each entry in 'to' must be a valid phone number in E.164 format (e.g. +14155551234)"]
},
"doc_url": "https://docs.sent.dm/reference/api/error-catalog"
}
}Solution: Use E.164 format for every entry in to:
{
"to": ["+14155551234"]
}Resource Not Found
Contact Not Found
{
"success": false,
"status": 404,
"error": {
"code": "RESOURCE_001",
"message": "Contact not found",
"doc_url": "https://docs.sent.dm/reference/api/error-catalog"
}
}Solution: Verify the contact ID exists:
curl -X GET https://api.sent.dm/v3/contacts \
-H "x-api-key: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"Rate Limiting
Too Many Requests
{
"success": false,
"status": 429,
"error": {
"code": "BUSINESS_002",
"message": "Rate limit exceeded",
"doc_url": "https://docs.sent.dm/reference/api/rate-limits"
}
}Response Headers:
Retry-After: 60
X-RateLimit-Limit: 10000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1705312800Solution: Implement exponential backoff and respect the Retry-After header. See Rate Limits for per-endpoint limit values and the full set of rate limit headers.
Related guides
- How to handle Sent API errors: success-flag checks, error-code branching, request-ID logging, client-side validation, and testing error paths with sandbox mode.
- How to retry Sent API requests safely: retry loops that use idempotency keys to prevent duplicate operations.
- How to handle Sent API rate limits: backoff, monitoring, and throttling for
429responses.
Data Models
Reference for the Sent API v3 response envelope, ApiError and ApiMeta objects, and pagination metadata, plus links to per-resource request and response schemas.
Error Catalog
Every error code the Sent API v3 can return, with the HTTP status it maps to, the cause behind it, and the steps to resolve it