Errors, Limits, and Retry
Error response format
Intentional API failures use the RFC 7807 ProblemDetails structure with the
application/problem+json media type. Branch on code and HTTP status,
not human-readable text, and tolerate codes added later.
{
"type": "https://arelhub.com/errors/quota-exceeded",
"title": "Quota exceeded",
"status": 429,
"detail": "Monthly message quota for the subaccount has been reached.",
"instance": "/v1/accounts/00000000-0000-0000-0000-000000000001/messages",
"code": "QUOTA_EXCEEDED",
"traceId": "00-abc123-def456-00",
"retryAfterSeconds": 30,
"dailyRemaining": 0,
"monthlyRemaining": 0
}
type, title, status, code, and traceId
are required. Validation failures may include an errors map; quota and throttling failures may
include retry and remaining-count extensions. Include traceId in support requests.
Numbered error catalog
Numeric IDs organize the catalog for documentation and support tooling. The wire contract uses the stable string code, not a numeric enum.
| Family | IDs | Codes |
|---|---|---|
| General / protocol | 1001-1005 | INVALID_INPUT, INVALID_OPERATION, NOT_FOUND, CONFLICT,
INTERNAL_ERROR
|
| Authentication | 1101-1108 | UNAUTHORIZED, TOKEN_EXPIRED, TOKEN_REVOKED,
API_KEY_INVALID, API_KEY_EXPIRED, API_KEY_REVOKED,
API_KEY_DELETED, INVALID_TOKEN
|
| Authorization / session | 1201-1205 | FORBIDDEN, CSRF_HEADER_REQUIRED, STEP_UP_REQUIRED,
SESSION_INVALID, SESSION_IDLE_TIMEOUT
|
| Validation | 1301-1304 | INVALID_JSON, VALIDATION_FAILED, BODY_REQUIRED,
INVALID_REQUEST
|
| Account / user state | 1401-1406 | USER_NOT_FOUND, USER_INACTIVE, ACCOUNT_LOCKED,
ACCOUNT_INACTIVE, STAFF_NOT_FOUND, STAFF_INACTIVE
|
| Multi-factor auth | 1411-1418 | MFA_REQUIRED, MFA_CHALLENGE_INVALID, MFA_ALREADY_ENABLED,
MFA_NOT_ENABLED, MFA_ENROLLMENT_REQUIRED, STEP_UP_FACTOR_REQUIRED,
MFA_REQUIRED_BY_ACCOUNT, WEBAUTHN_VERIFICATION_FAILED
|
| Security challenges | 1501-1502 | CAPTCHA_FAILED, IP_THROTTLED |
| Token / OTP / step-up | 1601-1607 | RATE_LIMITED, INVALID_CODE, LOCKED, ALREADY_USED,
INVALID_OR_EXPIRED, SUBJECT_MISMATCH, ACTION_MISMATCH
|
| Quotas / throttling | 1701-1702 | QUOTA_EXCEEDED, RATE_LIMIT_EXCEEDED |
| Billing / subscription | 1801 | NO_ACTIVE_SUBSCRIPTION |
| Messaging / compliance | 1901 | COMPLIANCE_REJECTED |
| Concurrency / persistence | 2001-2002 | CONCURRENCY_ERROR, DATABASE_ERROR |
| Reply options | 2203-2204 | REPLY_OPTION_RESERVED_KEYWORD, REPLY_OPTION_INVALID |
| Scheduled sends, expiry | 2201, 2202, 2205 | SEND_AT_OUT_OF_RANGE, EXPIRES_AT_INVALID, MESSAGE_NOT_CANCELABLE |
| Recipient consent | 2301-2304 | RECIPIENT_OPT_OUT_SCOPE_REQUIRED, RECIPIENT_NOT_OPTED_OUT, RECIPIENT_OPT_OUT_NOT_LIFTABLE, CONSENT_IMPORT_INVALID |
| Dependencies / timeouts | 2101-2103 | TIMEOUT, REQUEST_CANCELLED, EXTERNAL_SERVICE_ERROR |
Status code reference
| Code | Title | Recommended action |
|---|---|---|
400 |
Bad Request | Fix request syntax |
401 |
Unauthorized | Refresh token or verify API key |
403 |
Forbidden | Check API key rights or account policy |
404 |
Not Found | Verify ID and account ownership |
409 |
Conflict | Check existing state before retrying |
422 |
Validation Error | Inspect detail for constraint violation |
429 |
Too Many Requests | See rate limits below |
500 |
Internal Server Error | Retry with backoff; file support ticket if persistent |
Rate limits
When a rate limit is exceeded the response includes a Retry-After header (when present):
{
"type": "https://arelhub.com/errors/rate-limit-exceeded",
"title": "Rate limit exceeded",
"status": 429,
"detail": "Request rate limit exceeded. Retry after 30 seconds.",
"code": "RATE_LIMIT_EXCEEDED",
"traceId": "00-abc123-def456-00",
"retryAfterSeconds": 30
}
Quota limits
Quota depletion returns a distinct 429 with code QUOTA_EXCEEDED. Check remaining quota
before bulk sends:
GET /v1/user/quota/subaccounts/{subaccountId}
Authorization: Bearer eyJ...
Retry posture
| Error | Retry? | Strategy |
|---|---|---|
400, 422 |
No | Fix request and resubmit |
401 |
Conditional | Refresh token, then retry once |
403 |
No | Contact support if unexpected |
404 |
No | Verify resource ID |
429 |
Yes | Respect Retry-After; exponential backoff |
500 |
Yes | Exponential backoff with jitter; cap at 3-5 attempts |
| Network timeout | Yes | Treat timed-out requests as idempotent; check status first |
Exponential backoff formula
delay = min(base x 2^attempt, maxDelay) + random(0, jitter)
// Recommended: base=1s, maxDelay=30s, jitter=500ms
Idempotent requests
Message submission is not idempotent by default. If a POST /v1/accounts/{accountId}/messages
request times out, check status before retrying. Use the externalId field on submission to correlate
your internal request ID with ArelHub's message ID.
Support
For persistent errors or incidents, include in your support request: traceId, request timestamp
(UTC), endpoint and HTTP method, and anonymized request body.