Error Handling
Understand Trunx error responses, status codes, and retry patterns.
All error responses follow the RFC 7807 Problem Details format. Every error includes a machine-readable type URI, a human-readable title, the HTTP status code, and a detail message.
Error response format
{
"type": "https://api.trunx.io/errors/not-found",
"title": "Not Found",
"status": 404,
"detail": "The requested resource was not found."
}| Field | Type | Description |
|---|---|---|
type | string | URI identifying the error type |
title | string | Short human-readable summary |
status | number | HTTP status code |
detail | string | Explanation of what went wrong |
Status codes
| Code | Title | When it occurs |
|---|---|---|
| 400 | Bad Request | Invalid input, malformed JSON, E.164 format errors |
| 401 | Unauthorized | Missing or invalid API key |
| 403 | Forbidden | API key lacks the required scope for this action |
| 404 | Not Found | Resource does not exist |
| 409 | Conflict | Action conflicts with current state (e.g. starting an already-active campaign) |
| 422 | Unprocessable Entity | Request is well-formed but fails validation (e.g. invalid area code, missing required field) |
| 429 | Too Many Requests | Rate limit exceeded — check the Retry-After header |
| 500 | Internal Server Error | Unexpected server error |
Error types
| Type URI | Description |
|---|---|
https://api.trunx.io/errors/bad-request | Request body or query parameters are invalid |
https://api.trunx.io/errors/unauthorized | No API key provided or key is invalid |
https://api.trunx.io/errors/forbidden | Key is valid but lacks required scope |
https://api.trunx.io/errors/not-found | Resource not found |
https://api.trunx.io/errors/conflict | State conflict (e.g. campaign already started) |
https://api.trunx.io/errors/validation-error | Input validation failed |
https://api.trunx.io/errors/rate-limited | Too many requests |
https://api.trunx.io/errors/dnc-blocked | Number is on the suppression/DNC list |
https://api.trunx.io/errors/tcpa-blocked | Call blocked — outside legal calling hours |
https://api.trunx.io/errors/content-blocked | SMS content flagged by compliance scanner |
https://api.trunx.io/errors/channel-budget-exceeded | SIP channel pool exhausted |
https://api.trunx.io/errors/internal | Unexpected server error |
Retry patterns
429 Too Many Requests
Use the Retry-After response header to determine when to retry. If the header is not present, use exponential backoff starting at 1 second.
HTTP/1.1 429 Too Many Requests
Retry-After: 5
{
"type": "https://api.trunx.io/errors/rate-limited",
"title": "Too Many Requests",
"status": 429,
"detail": "Rate limit exceeded. Try again in 5 seconds."
}5xx Server Errors
Retry with exponential backoff. Start at 1 second, double each attempt, cap at 3 retries.
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const res = await fetch(url, options);
if (res.status === 429) {
const retryAfter = parseInt(res.headers.get("Retry-After") || "1", 10);
await new Promise((r) => setTimeout(r, retryAfter * 1000));
continue;
}
if (res.status >= 500 && attempt < maxRetries) {
await new Promise((r) => setTimeout(r, Math.pow(2, attempt) * 1000));
continue;
}
return res;
}
}import time
import requests
def fetch_with_retry(url, headers, max_retries=3):
for attempt in range(max_retries + 1):
res = requests.get(url, headers=headers)
if res.status_code == 429:
retry_after = int(res.headers.get("Retry-After", "1"))
time.sleep(retry_after)
continue
if res.status_code >= 500 and attempt < max_retries:
time.sleep(2 ** attempt)
continue
return res4xx Client Errors (except 429)
Do not retry. Fix the request and resubmit. Common fixes:
- 400: Check that phone numbers are in E.164 format and required fields are present
- 401: Verify your API key is correct and the
Authorizationheader is set - 403: Check that your API key has the required scopes for this endpoint
- 422: Review the
detailfield for specific validation errors
Guardrail errors
These errors are returned when infrastructure-level compliance checks block an action. They cannot be bypassed — the guardrails are enforced before the request reaches any provider.
DNC blocked
{
"type": "https://api.trunx.io/errors/dnc-blocked",
"title": "DNC Blocked",
"status": 403,
"detail": "The number +14155551234 is on the suppression list and cannot be contacted."
}The destination number is on your DNC/suppression list. Remove it from the suppression list first if the contact has opted back in.
TCPA blocked
{
"type": "https://api.trunx.io/errors/tcpa-blocked",
"title": "TCPA Blocked",
"status": 403,
"detail": "Calls to +14155551234 are not permitted outside the 9:00 AM - 9:00 PM window in the destination timezone."
}The call falls outside the legal calling hours for the destination's timezone. Adjust your campaign schedule or retry during permitted hours.
Content blocked
{
"type": "https://api.trunx.io/errors/content-blocked",
"title": "Content Blocked",
"status": 422,
"detail": "Message content was flagged by the compliance scanner for prohibited content."
}The outbound SMS message was flagged by the content scanner. Review and revise the message content.
Channel budget exceeded
{
"type": "https://api.trunx.io/errors/channel-budget-exceeded",
"title": "Channel Budget Exceeded",
"status": 429,
"detail": "All SIP channels are currently in use. Retry after active calls complete."
}The SIP channel pool is fully utilized. This is a transient condition — retry after a short delay as active calls complete and release channels.