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

FieldTypeDescription
successbooleanAlways false on an error response
statusintegerHTTP status code, repeated in the body
errorobjectThe error itself, described below
metaobjectRequest metadata, described below

An error response carries no data field.

Error Object Fields

FieldTypeDescription
codestringMachine-readable error code (for example, RESOURCE_001)
messagestringHuman-readable error message
detailsobjectField-level validation errors, as a map of field name to an array of messages. Omitted when there are none
doc_urlstringLink 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

FieldTypeDescription
request_idstringUnique request identifier for support and debugging
timestampstringISO 8601 timestamp of the error
versionstringAPI version (always v3)

HTTP Status Codes

StatusDescriptionCommon Causes
200 OKRequest successful-
201 CreatedResource created successfully-
204 No ContentRequest successful, no response bodyDELETE operations
400 Bad RequestInvalid request format or parametersMissing required fields, invalid JSON
401 UnauthorizedAuthentication failedMissing or invalid API key
403 ForbiddenPermission deniedInsufficient role permissions
404 Not FoundResource not foundInvalid resource ID
409 ConflictResource conflictDuplicate entry, concurrent modification
413 Payload Too LargeRequest body too largeBody exceeds the endpoint's size limit
422 Unprocessable EntityValidation failedInvalid field values, business rule violations, Idempotency-Key reused for a different request
429 Too Many RequestsRate limit exceededToo many requests in time window
500 Internal Server ErrorUnexpected server errorServer-side issue
501 Not ImplementedNot supported yetCompliance requirements requested for a channel other than SMS
502 Bad GatewayUpstream service errorProvider service unavailable
503 Service UnavailableService temporarily unavailableMaintenance 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:

CodeHTTP StatusMeaningHandling
AUTH_001401User is not authenticatedRenew the voice token. A missing or wrong API key returns AUTH_002 instead
AUTH_002401Invalid or missing API keySend the key in x-api-key or Authorization: Bearer. Verify it, and regenerate it if revoked
VALIDATION_001400Request validation failedCheck the details field for field-level errors
RESOURCE_001404Contact not foundVerify the contact ID exists and belongs to your account
BUSINESS_002429Rate limit exceededBack off and respect the Retry-After header
CONFLICT_001409Concurrent idempotent request in progressWait for the original request to complete before retrying
SERVICE_001503Cache service temporarily unavailableRetry the request after a short delay
INTERNAL_001500Unexpected internal server errorRetry, 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.

CodeTerminal statusreason_codeWhat it means
ERR_CONSENT_BLOCKEDFILTEREDBUSINESS_004The 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_DENIEDFILTEREDDELIVERY_004Routing rules denied every candidate route for this send, and no fallback was allowed.
ERR_TEMPLATE_PARAMS_INVALIDFAILEDVALIDATION_008Required template variables were missing or failed validation.

Read each message's outcome, and its reason_code and reason, through:

  • GET /v3/messages/{id}: the status field carries the terminal status, and reason_code and reason sit beside it and on the matching event
  • GET /v3/messages/{id}/activities: the FILTERED or FAILED activity carries the same pair
  • The message.filtered and message.failed webhook events, as payload.reason_code and payload.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.

CodeTerminal statusTriggerWhose rule
CONVERSATION_TEMPLATE_REQUIREDBLOCKEDFree-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_REQUIREDFAILEDFree-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_002

Solution: 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: 1705312800

Solution: 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.


On this page