Skip to Content
🎉 Welcome to Delivery Chat Documentation
V1Rest APIErrors & Rate Limiting

Errors & Rate Limiting

Error Response Format

All errors follow a consistent JSON format:

{ "error": "error_code", "message": "Human-readable description of what went wrong" }

HTTP Status Codes

StatusMeaningWhen
400Bad RequestMissing or invalid headers, malformed request body, validation failure
401UnauthorizedInvalid or missing API key, invalid App ID
403ForbiddenAttempting to edit/delete another user’s message
404Not FoundResource doesn’t exist or the visitor is not a participant
422Unprocessable EntityBusiness rule violation (closed conversation, expired edit window)
429Too Many RequestsRate limit exceeded
500Internal Server ErrorUnexpected server error

Error Codes

CodeHTTP StatusDescription
bad_request400Request validation failed — check headers and body
unauthorized401API key or App ID is missing or invalid
forbidden403You don’t have permission for this action
not_found404Resource not found or not accessible
conversation_not_active422Cannot send messages to a closed conversation
edit_window_expired422The 15-minute edit/delete window has passed
rate_limit_exceeded429Too many requests — back off and retry

Rate Limiting

The API enforces per-visitor rate limits using three sliding time windows. Limits are applied per visitor per application.

Rate Limit Windows

WindowPurpose
Per-secondPrevents burst abuse
Per-minuteControls sustained request rate
Per-hourCaps total volume

If any window is exceeded, the API returns 429 Too Many Requests.

Rate Limit Headers

On a 429 response, the following headers indicate which limit was hit:

HeaderDescriptionExample
Retry-AfterSeconds to wait before retrying5
X-RateLimit-LimitMax requests allowed in the violated window60
X-RateLimit-RemainingRemaining requests (always 0 on a 429)0
X-RateLimit-ResetUnix timestamp (seconds) when the window resets1705312500

429 Response Body

{ "error": "rate_limit_exceeded", "cause": "per_visitor", "retryAfter": 5, "window": "minute" }

Backoff Strategy

đź’ˇ

Recommended backoff

Use the Retry-After header to determine exactly how long to wait. If implementing exponential backoff, start at 1 second and cap at 60 seconds.

async function fetchWithRetry(url, options, maxRetries = 3) { for (let attempt = 0; attempt < maxRetries; attempt++) { const response = await fetch(url, options); if (response.status === 429) { const retryAfter = parseInt(response.headers.get("Retry-After") || "5"); await new Promise(resolve => setTimeout(resolve, retryAfter * 1000)); continue; } return response; } throw new Error("Max retries exceeded"); }

Common Error Scenarios

Missing X-Visitor-Id header
Scenario:
A request is made without the X-Visitor-Id header
Solution:
Include a valid UUID v4 in the X-Visitor-Id header with every request
{
"error": "bad_request",
"message": "X-Visitor-Id header required"
}
Invalid visitor ID format
Scenario:
X-Visitor-Id contains a non-UUID value
Solution:
Generate a proper UUID v4 (e.g. crypto.randomUUID() in JavaScript)
{
"error": "bad_request",
"message": "X-Visitor-Id must be a valid UUID"
}
Editing after 15 minutes
Scenario:
Attempting to edit a message sent more than 15 minutes ago
Solution:
Messages become immutable after 15 minutes. Display the edit window to users so they know the deadline.
{
"error": "edit_window_expired",
"message": "Message msg-uuid can no longer be modified. The 15-minute edit window expired at 2025-01-15T10:15:00.000Z."
}
Sending to a closed conversation
Scenario:
Posting a message to a conversation that has been resolved
Solution:
Create a new conversation instead. Closed conversations cannot receive new messages.
{
"error": "conversation_not_active",
"message": "Cannot send message: conversation is closed"
}
Last updated on