Error Codes Reference
This document provides a comprehensive reference for all error codes returned by the Relay API.
Error Response Format
REST API Errors (/api/v1)
{
"error": {
"code": "NOT_FOUND",
"message": "Ticket not found",
"details": {}
}
}
tRPC Errors (/api/trpc)
All API errors follow a consistent format:
{
"error": {
"message": "Human-readable error description",
"code": "ERROR_CODE",
"data": {
"code": "ERROR_CODE",
"httpStatus": 400,
"path": "procedure.name",
"stack": "..." // Only in development
}
}
}
Error Fields
| Field | Description |
|---|
message | Human-readable description of the error |
code | Machine-readable error code (see below) |
httpStatus | HTTP status code |
path | The tRPC procedure that returned the error |
HTTP Status Codes
| Status | Description |
|---|
400 | Bad Request - Invalid input parameters |
401 | Unauthorized - Missing or invalid authentication |
403 | Forbidden - Insufficient permissions |
404 | Not Found - Resource doesn't exist |
409 | Conflict - Resource already exists or state conflict |
429 | Too Many Requests - Rate limit exceeded |
500 | Internal Server Error - Unexpected server error |
Common Error Codes
Authentication Errors
| Code | HTTP | Description | Resolution |
|---|
UNAUTHORIZED | 401 | Not authenticated | Include valid session cookie or API key |
FORBIDDEN | 403 | Insufficient permissions | Check user role and permissions |
SESSION_EXPIRED | 401 | Session has expired | Re-authenticate to get new session |
INVALID_TOKEN | 401 | Invalid API token | Verify API key is correct |
Validation Errors
| Code | HTTP | Description | Resolution |
|---|
BAD_REQUEST | 400 | Invalid input parameters | Check request body against schema |
VALIDATION_ERROR | 400 | Input validation failed | Fix the specific validation issue |
INVALID_UUID | 400 | Invalid UUID format | Provide valid UUID string |
MISSING_FIELD | 400 | Required field is missing | Include all required fields |
Resource Errors
| Code | HTTP | Description | Resolution |
|---|
NOT_FOUND | 404 | Resource not found | Verify the resource ID exists |
ALREADY_EXISTS | 409 | Resource already exists | Use a different identifier |
CONFLICT | 409 | State conflict | Resolve the conflict and retry |
DELETED | 404 | Resource was deleted | Resource no longer available |
Rate Limiting
| Code | HTTP | Description | Resolution |
|---|
TOO_MANY_REQUESTS | 429 | Rate limit exceeded | Wait and retry with exponential backoff |
QUOTA_EXCEEDED | 429 | Usage quota exceeded | Upgrade plan or wait for reset |
Server Errors
| Code | HTTP | Description | Resolution |
|---|
INTERNAL_SERVER_ERROR | 500 | Unexpected server error | Contact support if persistent |
SERVICE_UNAVAILABLE | 503 | Service temporarily unavailable | Retry after a short delay |
TIMEOUT | 504 | Request timed out | Retry the request |
API-Specific Error Codes
Tickets API
| Code | Description |
|---|
TICKET_NOT_FOUND | Ticket with specified ID doesn't exist |
TICKET_ALREADY_CLOSED | Cannot modify a closed ticket |
TICKET_LOCKED | Ticket is locked for editing |
INVALID_STATUS | Invalid ticket status value |
INVALID_PRIORITY | Invalid priority value |
MERGE_SELF | Cannot merge ticket with itself |
MERGE_DIFFERENT_ORG | Cannot merge tickets from different organizations |
Conversations API
| Code | Description |
|---|
CONVERSATION_NOT_FOUND | Conversation doesn't exist |
ATTACHMENT_TOO_LARGE | File exceeds maximum size limit |
INVALID_ATTACHMENT_TYPE | File type not allowed |
EMPTY_CONTENT | Message content cannot be empty |
Customers API
| Code | Description |
|---|
CUSTOMER_NOT_FOUND | Customer doesn't exist |
EMAIL_ALREADY_EXISTS | Email address already in use |
INVALID_EMAIL | Email format is invalid |
CUSTOMER_HAS_TICKETS | Cannot delete customer with active tickets |
AI API
| Code | Description |
|---|
AI_NOT_AVAILABLE | AI features not available on current plan |
AI_LIMIT_EXCEEDED | AI usage limit exceeded for billing period |
AI_PROCESSING_FAILED | AI processing encountered an error |
INVALID_MODEL | Specified AI model not available |
Articles API
| Code | Description |
|---|
ARTICLE_NOT_FOUND | Article doesn't exist |
SLUG_EXISTS | Article slug already in use |
INVALID_STATUS | Invalid article status |
CONTENT_TOO_LARGE | Article content exceeds limit |
Automations API
| Code | Description |
|---|
AUTOMATION_NOT_FOUND | Automation doesn't exist |
INVALID_TRIGGER | Invalid trigger configuration |
INVALID_ACTION | Invalid action configuration |
INVALID_CONDITION | Invalid condition configuration |
CIRCULAR_REFERENCE | Automation would create circular reference |
Integrations API
| Code | Description |
|---|
INTEGRATION_NOT_FOUND | Integration doesn't exist |
INTEGRATION_DISABLED | Integration is disabled |
OAUTH_FAILED | OAuth authentication failed |
INVALID_CREDENTIALS | Invalid integration credentials |
SYNC_FAILED | Data synchronization failed |
RATE_LIMITED | External service rate limit hit |
Macros API
| Code | Description |
|---|
MACRO_NOT_FOUND | Macro doesn't exist |
INVALID_ACTION | Invalid macro action type |
APPLY_FAILED | Failed to apply macro to ticket |
SLA API
| Code | Description |
|---|
POLICY_NOT_FOUND | SLA policy doesn't exist |
TRACKING_NOT_FOUND | SLA tracking record doesn't exist |
ALREADY_PAUSED | SLA tracking is already paused |
NOT_PAUSED | SLA tracking is not paused |
Scheduling API
| Code | Description |
|---|
REMINDER_NOT_FOUND | Reminder doesn't exist |
MESSAGE_NOT_FOUND | Scheduled message doesn't exist |
INVALID_TIME | Specified time is in the past |
SCHEDULE_CONFLICT | Schedule conflicts with existing |
HEARTBEAT_TOO_FREQUENT | Heartbeat rate limit (30s minimum) |
Collections API
| Code | Description |
|---|
COLLECTION_NOT_FOUND | Collection doesn't exist |
SLUG_EXISTS | Collection slug already in use |
Webhooks
| Code | Description |
|---|
WEBHOOK_NOT_FOUND | Webhook doesn't exist |
INVALID_URL | Webhook URL is invalid |
DELIVERY_FAILED | Webhook delivery failed |
SIGNATURE_INVALID | Webhook signature verification failed |
Error Handling Best Practices
1. Check Error Code First
try {
const result = await trpc.tickets.get.query({ id: ticketId })
} catch (error) {
if (error.data?.code === 'NOT_FOUND') {
// Handle not found case
showNotification('Ticket not found')
} else if (error.data?.code === 'FORBIDDEN') {
// Handle permission error
redirectToLogin()
} else {
// Handle unexpected error
showErrorMessage(error.message)
}
}
2. Handle Rate Limits with Backoff
async function fetchWithRetry(fn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn()
} catch (error) {
if (error.data?.code === 'TOO_MANY_REQUESTS') {
const delay = Math.pow(2, i) * 1000 // Exponential backoff
await new Promise(resolve => setTimeout(resolve, delay))
} else {
throw error
}
}
}
throw new Error('Max retries exceeded')
}
3. Validate Input Before Sending
// Validate locally before API call
if (!isValidUUID(ticketId)) {
throw new Error('Invalid ticket ID format')
}
if (!email.includes('@')) {
throw new Error('Invalid email format')
}
4. Log Errors for Debugging
catch (error) {
console.error('API Error:', {
code: error.data?.code,
message: error.message,
path: error.data?.path,
httpStatus: error.data?.httpStatus
})
}
Rate Limits Summary
| Endpoint Category | Rate Limit |
|---|
| Read operations | 100 per minute |
| Write operations | 50 per minute |
| Search operations | 30 per minute |
| AI operations | 100 per minute (subject to plan limits) |
| Bulk operations | 10 per minute |
| Webhooks | 1000 per hour per endpoint |
| Heartbeat | 1 per 30 seconds |
Rate Limit Headers
When approaching rate limits, the API returns headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1705312800
Troubleshooting Common Errors
"UNAUTHORIZED" on Every Request
- Check that your session cookie is included
- Verify the cookie hasn't expired
- Ensure you're using HTTPS
- Check that the cookie domain matches
"NOT_FOUND" for Existing Resources
- Verify the resource ID is correct
- Check you're querying the right organization
- Ensure the resource hasn't been soft-deleted
- Verify your user has access to the resource
"BAD_REQUEST" Validation Errors
- Check all required fields are present
- Verify field types match the schema
- Ensure strings meet length requirements
- Check enum values are valid
"INTERNAL_SERVER_ERROR" Responses
- Retry the request once
- Check the API status page
- If persistent, contact support with the error details
- Include the timestamp and request path