Skip to content

REST API - Error Handling

Comprehensive guide to understanding and handling API errors, including error codes, messages, and best practices.

📋 Table of Contents


Overview

The HelpDesk Pro API uses standard HTTP status codes and provides detailed error messages to help you understand and resolve issues quickly.

Error Response Structure

All error responses follow a consistent format:

json
{
  "success": false,
  "message": "Error description",
  "errors": {
    "field_name": ["Error message for this field"]
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00.000000Z",
    "version": "v1"
  }
}

Error Response Format

Standard Error Response

json
{
  "success": false,
  "message": "The given data was invalid.",
  "errors": {
    "email": ["The email field is required."],
    "password": ["The password must be at least 8 characters."]
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00.000000Z",
    "version": "v1"
  }
}

Simple Error Response

For errors without field-specific issues:

json
{
  "success": false,
  "message": "Resource not found",
  "meta": {
    "timestamp": "2025-01-15T10:30:00.000000Z",
    "version": "v1"
  }
}

HTTP Status Codes

2xx Success

CodeMeaningDescription
200OKRequest successful
201CreatedResource created successfully
204No ContentRequest successful, no content to return

4xx Client Errors

CodeMeaningDescription
400Bad RequestInvalid request format or parameters
401UnauthorizedAuthentication required or token invalid
403ForbiddenUser lacks permission for this resource
404Not FoundRequested resource doesn't exist
422Unprocessable EntityValidation failed
429Too Many RequestsRate limit exceeded

5xx Server Errors

CodeMeaningDescription
500Internal Server ErrorUnexpected server error
503Service UnavailableService temporarily unavailable

Common Error Scenarios

401 Unauthorized

Cause: Missing or invalid authentication token.

Response:

json
{
  "success": false,
  "message": "Unauthenticated",
  "meta": {
    "timestamp": "2025-01-15T10:30:00.000000Z",
    "version": "v1"
  }
}

Solution:

  • Verify token is included in Authorization header
  • Check token format: Bearer {token}
  • Re-authenticate to get a new token
  • Ensure token hasn't been revoked

Example Handling:

javascript
if (response.status === 401) {
  // Clear stored token
  localStorage.removeItem('api_token');
  // Redirect to login
  window.location.href = '/login';
}

403 Forbidden

Cause: User doesn't have permission to access the resource.

Response:

json
{
  "success": false,
  "message": "This action is unauthorized.",
  "meta": {
    "timestamp": "2025-01-15T10:30:00.000000Z",
    "version": "v1"
  }
}

Solution:

  • Verify user role has required permissions
  • Check resource ownership (for user-specific resources)
  • Contact administrator for permission changes

Example Handling:

javascript
if (response.status === 403) {
  showError('You do not have permission to perform this action.');
}

404 Not Found

Cause: Requested resource doesn't exist.

Response:

json
{
  "success": false,
  "message": "Ticket not found",
  "meta": {
    "timestamp": "2025-01-15T10:30:00.000000Z",
    "version": "v1"
  }
}

Solution:

  • Verify resource ID is correct
  • Check if resource was deleted
  • Ensure you're using the correct endpoint

Example Handling:

javascript
if (response.status === 404) {
  showError('The requested resource was not found.');
}

422 Validation Error

Cause: Request data failed validation.

Response:

json
{
  "success": false,
  "message": "The given data was invalid.",
  "errors": {
    "email": [
      "The email field is required.",
      "The email must be a valid email address."
    ],
    "password": [
      "The password must be at least 8 characters.",
      "The password confirmation does not match."
    ]
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00.000000Z",
    "version": "v1"
  }
}

Solution:

  • Review error messages for each field
  • Ensure all required fields are provided
  • Verify data types match expected format
  • Check field-specific validation rules

Example Handling:

javascript
if (response.status === 422) {
  const errors = response.data.errors;
  // Display field-specific errors
  Object.keys(errors).forEach(field => {
    showFieldError(field, errors[field][0]);
  });
}

429 Too Many Requests

Cause: Rate limit exceeded.

Response Headers:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
Retry-After: 15

Response:

json
{
  "success": false,
  "message": "Too Many Requests",
  "meta": {
    "timestamp": "2025-01-15T10:30:00.000000Z",
    "version": "v1"
  }
}

Solution:

  • Wait for rate limit window to reset
  • Check Retry-After header for wait time
  • Implement request throttling
  • Reduce request frequency

Example Handling:

javascript
if (response.status === 429) {
  const retryAfter = response.headers.get('Retry-After');
  const waitTime = parseInt(retryAfter) * 1000;
  
  setTimeout(() => {
    // Retry request
    retryRequest();
  }, waitTime);
}

500 Internal Server Error

Cause: Unexpected server error.

Response:

json
{
  "success": false,
  "message": "Internal server error",
  "meta": {
    "timestamp": "2025-01-15T10:30:00.000000Z",
    "version": "v1"
  }
}

Solution:

  • Retry request after a short delay
  • Check API status page
  • Contact support if issue persists
  • Report the error with request details

Validation Errors

Validation errors provide detailed information about what went wrong with your request data.

Field-Level Errors

Each field can have multiple error messages:

json
{
  "errors": {
    "email": [
      "The email field is required.",
      "The email must be a valid email address."
    ],
    "password": [
      "The password must be at least 8 characters."
    ]
  }
}

Common Validation Rules

FieldRulesExample Error
emailrequired, email format"The email must be a valid email address."
passwordrequired, min:8"The password must be at least 8 characters."
subjectrequired, max:255"The subject may not be greater than 255 characters."
priority_idexists in priorities table"The selected priority id is invalid."
status_idexists in status table"The selected status id is invalid."

Handling Validation Errors

JavaScript Example:

javascript
function handleValidationErrors(errors) {
  const errorMessages = {};
  
  Object.keys(errors).forEach(field => {
    errorMessages[field] = errors[field][0]; // Get first error message
  });
  
  return errorMessages;
}

// Usage
try {
  const response = await api.createTicket(ticketData);
} catch (error) {
  if (error.response?.status === 422) {
    const fieldErrors = handleValidationErrors(error.response.data.errors);
    // Display errors in form
    displayFormErrors(fieldErrors);
  }
}

React Example:

jsx
const [errors, setErrors] = useState({});

const handleSubmit = async (formData) => {
  try {
    await api.createTicket(formData);
    setErrors({});
  } catch (error) {
    if (error.response?.status === 422) {
      const fieldErrors = {};
      Object.keys(error.response.data.errors).forEach(field => {
        fieldErrors[field] = error.response.data.errors[field][0];
      });
      setErrors(fieldErrors);
    }
  }
};

// In JSX
{errors.email && <div className="error">{errors.email}</div>}

Error Handling Examples

JavaScript/TypeScript

javascript
class APIError extends Error {
  constructor(message, status, errors = {}) {
    super(message);
    this.name = 'APIError';
    this.status = status;
    this.errors = errors;
  }
}

async function handleAPIRequest(requestFn) {
  try {
    const response = await requestFn();
    return response.data;
  } catch (error) {
    if (error.response) {
      // Server responded with error
      const { status, data } = error.response;
      
      switch (status) {
        case 401:
          // Unauthorized
          localStorage.removeItem('api_token');
          window.location.href = '/login';
          throw new APIError('Please log in again', 401);
          
        case 403:
          // Forbidden
          throw new APIError('You do not have permission', 403);
          
        case 404:
          // Not Found
          throw new APIError(data.message || 'Resource not found', 404);
          
        case 422:
          // Validation Error
          throw new APIError(
            data.message || 'Validation failed',
            422,
            data.errors || {}
          );
          
        case 429:
          // Rate Limited
          const retryAfter = error.response.headers['retry-after'];
          throw new APIError(
            `Rate limit exceeded. Retry after ${retryAfter} seconds`,
            429
          );
          
        default:
          throw new APIError(
            data.message || 'An error occurred',
            status
          );
      }
    } else if (error.request) {
      // Request made but no response
      throw new APIError('Network error. Please check your connection.', 0);
    } else {
      // Error setting up request
      throw new APIError('Request setup error', 0);
    }
  }
}

// Usage
try {
  const tickets = await handleAPIRequest(() => 
    api.get('/tickets')
  );
} catch (error) {
  if (error instanceof APIError) {
    console.error(`API Error (${error.status}):`, error.message);
    if (error.errors) {
      console.error('Field errors:', error.errors);
    }
  }
}

PHP

php
class APIException extends Exception {
    public $statusCode;
    public $errors;

    public function __construct($message, $statusCode = 400, $errors = []) {
        parent::__construct($message);
        $this->statusCode = $statusCode;
        $this->errors = $errors;
    }
}

function handleAPIRequest($requestFn) {
    try {
        $response = $requestFn();
        return $response;
    } catch (Exception $e) {
        if ($e instanceof APIException) {
            throw $e;
        }
        
        // Handle HTTP errors
        if (method_exists($e, 'getResponse')) {
            $response = $e->getResponse();
            $statusCode = $response->getStatusCode();
            $data = json_decode($response->getBody(), true);
            
            switch ($statusCode) {
                case 401:
                    // Unauthorized
                    throw new APIException('Please log in again', 401);
                    
                case 403:
                    // Forbidden
                    throw new APIException('You do not have permission', 403);
                    
                case 404:
                    // Not Found
                    throw new APIException(
                        $data['message'] ?? 'Resource not found',
                        404
                    );
                    
                case 422:
                    // Validation Error
                    throw new APIException(
                        $data['message'] ?? 'Validation failed',
                        422,
                        $data['errors'] ?? []
                    );
                    
                default:
                    throw new APIException(
                        $data['message'] ?? 'An error occurred',
                        $statusCode
                    );
            }
        }
        
        throw new APIException('Network error', 0);
    }
}

Python

python
class APIError(Exception):
    def __init__(self, message, status_code=400, errors=None):
        self.message = message
        self.status_code = status_code
        self.errors = errors or {}
        super().__init__(self.message)

def handle_api_request(request_fn):
    try:
        response = request_fn()
        return response.json()
    except requests.exceptions.HTTPError as e:
        status_code = e.response.status_code
        data = e.response.json()
        
        if status_code == 401:
            # Unauthorized
            raise APIError('Please log in again', 401)
        elif status_code == 403:
            # Forbidden
            raise APIError('You do not have permission', 403)
        elif status_code == 404:
            # Not Found
            raise APIError(
                data.get('message', 'Resource not found'),
                404
            )
        elif status_code == 422:
            # Validation Error
            raise APIError(
                data.get('message', 'Validation failed'),
                422,
                data.get('errors', {})
            )
        elif status_code == 429:
            # Rate Limited
            retry_after = e.response.headers.get('Retry-After', '60')
            raise APIError(
                f'Rate limit exceeded. Retry after {retry_after} seconds',
                429
            )
        else:
            raise APIError(
                data.get('message', 'An error occurred'),
                status_code
            )
    except requests.exceptions.RequestException as e:
        raise APIError('Network error', 0)

Best Practices

1. Always Check Response Status

javascript
const response = await fetch(url, options);
if (!response.ok) {
  throw new Error(`HTTP error! status: ${response.status}`);
}

2. Provide User-Friendly Messages

javascript
const errorMessages = {
  401: 'Please log in to continue',
  403: 'You do not have permission for this action',
  404: 'The requested item was not found',
  422: 'Please check your input and try again',
  429: 'Too many requests. Please wait a moment',
  500: 'Server error. Please try again later',
};

function getUserFriendlyMessage(statusCode) {
  return errorMessages[statusCode] || 'An error occurred';
}

3. Log Errors for Debugging

javascript
function logError(error, context) {
  console.error('API Error:', {
    message: error.message,
    status: error.status,
    context: context,
    timestamp: new Date().toISOString(),
  });
  
  // Send to error tracking service
  // errorTrackingService.log(error, context);
}

4. Implement Retry Logic

javascript
async function requestWithRetry(url, options, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fetch(url, options);
      if (response.ok) return response;
      
      // Don't retry client errors (4xx)
      if (response.status >= 400 && response.status < 500) {
        throw new Error(`Client error: ${response.status}`);
      }
      
      // Retry server errors (5xx)
      if (i < maxRetries - 1) {
        await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
        continue;
      }
      
      throw new Error(`Server error: ${response.status}`);
    } catch (error) {
      if (i === maxRetries - 1) throw error;
    }
  }
}

Troubleshooting

Common Issues

"Unauthenticated" Error

Problem: Token is missing or invalid.

Solution:

  1. Verify token is included in Authorization header
  2. Check token format: Bearer {token}
  3. Re-authenticate to get a new token

"Validation Failed" Error

Problem: Request data doesn't meet validation requirements.

Solution:

  1. Check error messages for each field
  2. Verify required fields are provided
  3. Ensure data types are correct
  4. Review validation rules in documentation

"Too Many Requests" Error

Problem: Rate limit exceeded.

Solution:

  1. Wait for rate limit window to reset
  2. Check Retry-After header
  3. Implement request throttling
  4. Reduce request frequency

Network Errors

Problem: Request fails before reaching server.

Solution:

  1. Check internet connection
  2. Verify API base URL is correct
  3. Check firewall/proxy settings
  4. Test with cURL to isolate issue

Next Steps


For additional support with error handling, contact our API support team.

Released under the MIT License.