Every senderZ API response follows a consistent envelope format. Understanding this format makes error handling predictable across all endpoints.
Response Envelope
All API responses use this JSON structure:
{
"data": {},
"error": "Human-readable error message",
"code": "MACHINE_READABLE_ERROR_CODE"
}
| Field | Present When | Description |
|---|---|---|
data | Success responses | The response payload. Shape varies by endpoint. |
error | Error responses | A plain-English explanation of what went wrong. Safe to display to end users. |
code | Error responses | A stable, machine-readable error code. Use this for programmatic error handling. |
On success, error and code are absent. On error, data is absent.
Success Example
{
"data": {
"message_id": "01JMSG123ABC",
"status": "queued"
}
}
Error Example
{
"error": "The recipient has opted out of messages from your account.",
"code": "RECIPIENT_OPTED_OUT"
}
HTTP Status Codes
senderZ uses standard HTTP status codes. Here is what each one means in context:
Success Codes
| Status | Meaning | When You See It |
|---|---|---|
200 OK | Request succeeded | GET requests, updates, deletions |
201 Created | Resource created | POST requests that create new records (contacts, API keys, templates) |
202 Accepted | Queued for processing | Message sends and campaign launches — the message is enqueued but not yet delivered |
Client Error Codes
| Status | Meaning | When You See It |
|---|---|---|
400 Bad Request | Invalid input | Missing required fields, malformed JSON, invalid phone number format |
401 Unauthorized | Auth failed | Missing or invalid API key, expired Clerk JWT |
403 Forbidden | Access denied | API key is deactivated, tenant is suspended, or accessing another tenant’s data |
404 Not Found | Resource missing | The ID does not exist or belongs to a different tenant |
409 Conflict | Duplicate | Creating a contact with a phone number that already exists |
429 Too Many Requests | Rate limited | You exceeded your plan’s rate limit. Check the Retry-After header. |
Server Error Codes
| Status | Meaning | When You See It |
|---|---|---|
500 Internal Server Error | Server failure | Unexpected error. These are logged and investigated. If persistent, contact support. |
503 Service Unavailable | Temporary outage | The iMessage engine or a downstream service is temporarily unreachable. Retry after a short delay. |
Typed Error Codes
Every error response includes a code field with a stable string identifier. These codes never change between API versions, making them safe to match against in your code.
Authentication Errors
| Code | HTTP Status | Description |
|---|---|---|
INVALID_API_KEY | 401 | API key is missing, malformed, or has been revoked |
KEY_INACTIVE | 403 | API key exists but has been deactivated |
TRIAL_EXPIRED | 403 | Your 14-day trial has ended and no subscription is active. Subscribe to restore access. |
TENANT_SUSPENDED | 403 | Your account has been suspended by the operator |
Validation Errors
| Code | HTTP Status | Description |
|---|---|---|
VALIDATION_ERROR | 400 | One or more request fields are invalid. The error message describes which field and why. |
INVALID_PHONE_NUMBER | 400 | Phone number is not in valid E.164 format (must start with + and contain 10-15 digits) |
INVALID_TEMPLATE | 400 | The referenced template name does not exist in your tenant |
MISSING_TEMPLATE_VAR | 400 | A required {{variable}} in the template has no matching value in the data object |
Messaging Errors
| Code | HTTP Status | Description |
|---|---|---|
RECIPIENT_OPTED_OUT | 400 | The recipient has sent STOP and opted out. You cannot message them until they send START. |
NO_PHONE_AVAILABLE | 503 | No phone is available to send this message. All assigned phones may be at their daily limit or offline. |
DELIVERY_FAILED | 500 | The message was sent but delivery to the recipient failed. Check the message status endpoint for details. |
QUIET_HOURS | 200 | The message was accepted but will be delivered after quiet hours end (8 AM recipient local time). This is not an error — the message is queued. |
Quota Errors
| Code | HTTP Status | Description |
|---|---|---|
RATE_LIMIT_EXCEEDED | 429 | You are sending requests faster than your plan allows. Wait for the Retry-After period. |
QUOTA_EXCEEDED | 429 | You have reached your monthly API call limit or daily new-contact limit. |
NEW_CONTACT_LIMIT | 429 | You have reached your daily new-contact limit. Messages to existing contacts are still allowed. |
Resource Errors
| Code | HTTP Status | Description |
|---|---|---|
NOT_FOUND | 404 | The requested resource does not exist |
CONTACT_NOT_FOUND | 404 | The contact ID does not exist in your tenant |
CAMPAIGN_NOT_FOUND | 404 | The campaign ID does not exist in your tenant |
DUPLICATE_CONTACT | 409 | A contact with this phone number already exists. The response includes the existing contact’s ID. |
Detection Errors
| Code | HTTP Status | Description |
|---|---|---|
DETECTION_UNAVAILABLE | 503 | The iMessage detection service is temporarily unreachable. Retry after a short delay. |
Handling Errors in Code
Use the code field for reliable error handling — never match against the human-readable error string, which may change.
const res = await fetch('https://api.senderz.com/v1/messages', {
method: 'POST',
headers: {
'Authorization': 'Bearer sz_live_YOUR_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: '+15551234567',
channel: 'auto',
body: 'Hello!',
}),
})
const result = await res.json()
if (!res.ok) {
switch (result.code) {
case 'RECIPIENT_OPTED_OUT':
// Remove this number from your send list
break
case 'TRIAL_EXPIRED':
// Redirect user to subscribe
break
case 'RATE_LIMIT_EXCEEDED':
// Wait and retry
const retryAfter = res.headers.get('Retry-After')
break
case 'NEW_CONTACT_LIMIT':
// Queue for tomorrow or upgrade plan
break
default:
console.error(`API error: ${result.code} — ${result.error}`)
}
}
Retry Strategy
Not all errors should be retried. Here is a quick guide:
| Error Type | Retry? | Strategy |
|---|---|---|
400 validation errors | No | Fix the request body |
401 / 403 auth errors | No | Check your API key or subscription status |
404 not found | No | Verify the resource ID |
409 conflict | No | Use the existing resource |
429 rate limit | Yes | Wait for Retry-After seconds, then retry |
500 server error | Yes | Retry with exponential backoff (1s, 2s, 4s), max 3 attempts |
503 service unavailable | Yes | Retry with exponential backoff (2s, 4s, 8s), max 5 attempts |
Frequently Asked Questions
What does TRIAL_EXPIRED mean? Your 14-day free trial has ended and you have not subscribed to a plan. All API requests will return this error until you choose a plan (Starter, Growth, or Scale) from the developer portal.
Why do I get RECIPIENT_OPTED_OUT when I know the number is valid? The recipient has previously sent a STOP keyword to your tenant’s phone number. senderZ automatically blocks all future messages to opted-out numbers. The recipient must send START to re-subscribe.
How do I know which field caused a VALIDATION_ERROR?
The error field in the response describes exactly which field is invalid and why. For example: “The ‘to’ field must be a valid E.164 phone number starting with +”.
What is the difference between QUOTA_EXCEEDED and NEW_CONTACT_LIMIT? QUOTA_EXCEEDED means you hit your monthly API call ceiling. NEW_CONTACT_LIMIT means you reached your daily limit for messaging phone numbers you have never contacted before. Messages to existing contacts are always unlimited.
Can I get more details about a DELIVERY_FAILED error?
Yes. Use GET /v1/messages/:id to retrieve the full message record, which includes a failure_reason field with carrier-level error details when available.