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 |
422 Unprocessable Entity | Validation failed | Invalid field values, business rule violations |
429 Too Many Requests | Rate limit exceeded | Too many requests in time window |
500 Internal Server Error | Unexpected server error | Server-side issue |
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 | Include the x-api-key header with a valid API key |
AUTH_002 | 401 | Invalid or missing API key | Verify the key; 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:
| Code | Terminal status | Trigger | What it means |
|---|---|---|---|
ERR_CONSENT_BLOCKED | FILTERED | Per-message consent gate | 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 | Per-message routing gate | Routing rules denied every candidate route for this send, and no fallback was allowed. |
ERR_TEMPLATE_PARAMS_INVALID | FAILED | Per-message template validation | Required template variables were missing or failed validation. |
Track each message's outcome through:
GET /v3/messages/{id}: thestatusfield carries the terminal status (FILTEREDorFAILED)GET /v3/messages/{id}/activities: aFILTEREDorFAILEDactivity; activitydescriptionvalues are generic (Message updated to FILTERED) and do not carry the ERR_* code or reason- The
message.filteredandmessage.failedwebhook events
The ERR_* code and detailed reason are recorded internally and are not currently included in API responses or webhook payloads. Contact support@sent.dm with the message ID if you need the exact reason.
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 |
Like the ERR_* codes, neither is currently exposed in API responses, webhook payloads, or the dashboard. The client-visible signal is the terminal status, which differs between them: the conversation gate records BLOCKED (excluded from your deliverability rate, fires message.blocked), while the WhatsApp gate records FAILED. Infer the cause from the send conditions, or contact support@sent.dm with the message ID. 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_001Solution: Include the x-api-key header:
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: 200
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