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

FieldDescription
messageHuman-readable description of the error
codeMachine-readable error code (see below)
httpStatusHTTP status code
pathThe tRPC procedure that returned the error

HTTP Status Codes

StatusDescription
400Bad Request - Invalid input parameters
401Unauthorized - Missing or invalid authentication
403Forbidden - Insufficient permissions
404Not Found - Resource doesn't exist
409Conflict - Resource already exists or state conflict
429Too Many Requests - Rate limit exceeded
500Internal Server Error - Unexpected server error

Common Error Codes

Authentication Errors

CodeHTTPDescriptionResolution
UNAUTHORIZED401Not authenticatedInclude valid session cookie or API key
FORBIDDEN403Insufficient permissionsCheck user role and permissions
SESSION_EXPIRED401Session has expiredRe-authenticate to get new session
INVALID_TOKEN401Invalid API tokenVerify API key is correct

Validation Errors

CodeHTTPDescriptionResolution
BAD_REQUEST400Invalid input parametersCheck request body against schema
VALIDATION_ERROR400Input validation failedFix the specific validation issue
INVALID_UUID400Invalid UUID formatProvide valid UUID string
MISSING_FIELD400Required field is missingInclude all required fields

Resource Errors

CodeHTTPDescriptionResolution
NOT_FOUND404Resource not foundVerify the resource ID exists
ALREADY_EXISTS409Resource already existsUse a different identifier
CONFLICT409State conflictResolve the conflict and retry
DELETED404Resource was deletedResource no longer available

Rate Limiting

CodeHTTPDescriptionResolution
TOO_MANY_REQUESTS429Rate limit exceededWait and retry with exponential backoff
QUOTA_EXCEEDED429Usage quota exceededUpgrade plan or wait for reset

Server Errors

CodeHTTPDescriptionResolution
INTERNAL_SERVER_ERROR500Unexpected server errorContact support if persistent
SERVICE_UNAVAILABLE503Service temporarily unavailableRetry after a short delay
TIMEOUT504Request timed outRetry the request

API-Specific Error Codes

Tickets API

CodeDescription
TICKET_NOT_FOUNDTicket with specified ID doesn't exist
TICKET_ALREADY_CLOSEDCannot modify a closed ticket
TICKET_LOCKEDTicket is locked for editing
INVALID_STATUSInvalid ticket status value
INVALID_PRIORITYInvalid priority value
MERGE_SELFCannot merge ticket with itself
MERGE_DIFFERENT_ORGCannot merge tickets from different organizations

Conversations API

CodeDescription
CONVERSATION_NOT_FOUNDConversation doesn't exist
ATTACHMENT_TOO_LARGEFile exceeds maximum size limit
INVALID_ATTACHMENT_TYPEFile type not allowed
EMPTY_CONTENTMessage content cannot be empty

Customers API

CodeDescription
CUSTOMER_NOT_FOUNDCustomer doesn't exist
EMAIL_ALREADY_EXISTSEmail address already in use
INVALID_EMAILEmail format is invalid
CUSTOMER_HAS_TICKETSCannot delete customer with active tickets

AI API

CodeDescription
AI_NOT_AVAILABLEAI features not available on current plan
AI_LIMIT_EXCEEDEDAI usage limit exceeded for billing period
AI_PROCESSING_FAILEDAI processing encountered an error
INVALID_MODELSpecified AI model not available

Articles API

CodeDescription
ARTICLE_NOT_FOUNDArticle doesn't exist
SLUG_EXISTSArticle slug already in use
INVALID_STATUSInvalid article status
CONTENT_TOO_LARGEArticle content exceeds limit

Automations API

CodeDescription
AUTOMATION_NOT_FOUNDAutomation doesn't exist
INVALID_TRIGGERInvalid trigger configuration
INVALID_ACTIONInvalid action configuration
INVALID_CONDITIONInvalid condition configuration
CIRCULAR_REFERENCEAutomation would create circular reference

Integrations API

CodeDescription
INTEGRATION_NOT_FOUNDIntegration doesn't exist
INTEGRATION_DISABLEDIntegration is disabled
OAUTH_FAILEDOAuth authentication failed
INVALID_CREDENTIALSInvalid integration credentials
SYNC_FAILEDData synchronization failed
RATE_LIMITEDExternal service rate limit hit

Macros API

CodeDescription
MACRO_NOT_FOUNDMacro doesn't exist
INVALID_ACTIONInvalid macro action type
APPLY_FAILEDFailed to apply macro to ticket

SLA API

CodeDescription
POLICY_NOT_FOUNDSLA policy doesn't exist
TRACKING_NOT_FOUNDSLA tracking record doesn't exist
ALREADY_PAUSEDSLA tracking is already paused
NOT_PAUSEDSLA tracking is not paused

Scheduling API

CodeDescription
REMINDER_NOT_FOUNDReminder doesn't exist
MESSAGE_NOT_FOUNDScheduled message doesn't exist
INVALID_TIMESpecified time is in the past
SCHEDULE_CONFLICTSchedule conflicts with existing
HEARTBEAT_TOO_FREQUENTHeartbeat rate limit (30s minimum)

Collections API

CodeDescription
COLLECTION_NOT_FOUNDCollection doesn't exist
SLUG_EXISTSCollection slug already in use

Webhooks

CodeDescription
WEBHOOK_NOT_FOUNDWebhook doesn't exist
INVALID_URLWebhook URL is invalid
DELIVERY_FAILEDWebhook delivery failed
SIGNATURE_INVALIDWebhook 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 CategoryRate Limit
Read operations100 per minute
Write operations50 per minute
Search operations30 per minute
AI operations100 per minute (subject to plan limits)
Bulk operations10 per minute
Webhooks1000 per hour per endpoint
Heartbeat1 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

  1. Check that your session cookie is included
  2. Verify the cookie hasn't expired
  3. Ensure you're using HTTPS
  4. Check that the cookie domain matches

"NOT_FOUND" for Existing Resources

  1. Verify the resource ID is correct
  2. Check you're querying the right organization
  3. Ensure the resource hasn't been soft-deleted
  4. Verify your user has access to the resource

"BAD_REQUEST" Validation Errors

  1. Check all required fields are present
  2. Verify field types match the schema
  3. Ensure strings meet length requirements
  4. Check enum values are valid

"INTERNAL_SERVER_ERROR" Responses

  1. Retry the request once
  2. Check the API status page
  3. If persistent, contact support with the error details
  4. Include the timestamp and request path